Compare commits
42 Commits
Hargata/be
...
v1.4.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf954a2946 | ||
|
|
d3f29be227 | ||
|
|
5afe88a33a | ||
|
|
6937eff576 | ||
|
|
6371cccc48 | ||
|
|
fc174160e0 | ||
|
|
876f99fd26 | ||
|
|
75c65b4681 | ||
|
|
76031d27d7 | ||
|
|
4079a93c3e | ||
|
|
d913ab2009 | ||
|
|
6bfbcc4374 | ||
|
|
73d9a7e6e9 | ||
|
|
3cbce8b584 | ||
|
|
e4fcb52b24 | ||
|
|
ae4f01ac9a | ||
|
|
46b3845b3e | ||
|
|
a9e4be823d | ||
|
|
56cae008a6 | ||
|
|
c2dd379ea3 | ||
|
|
baa569b323 | ||
|
|
64ce0f8c07 | ||
|
|
bd98e5a6cd | ||
|
|
83fc8b8682 | ||
|
|
89fa15bbb7 | ||
|
|
5e69be56aa | ||
|
|
b1112dc617 | ||
|
|
5a31460afe | ||
|
|
2bcedbc7d4 | ||
|
|
5047fdf1bc | ||
|
|
7c8c3fb1c8 | ||
|
|
9920bd472f | ||
|
|
d7839a8a05 | ||
|
|
7e07e73ef5 | ||
|
|
72a5960d40 | ||
|
|
3429f1e4f9 | ||
|
|
f8bea8bf81 | ||
|
|
43794dd223 | ||
|
|
5cc84a7b46 | ||
|
|
48248a4386 | ||
|
|
95305402e6 | ||
|
|
29f24c527f |
@@ -11,10 +11,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
<PackageReference Include="CsvHelper" Version="33.0.1" />
|
||||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||||
<PackageReference Include="MailKit" Version="4.8.0" />
|
<PackageReference Include="MailKit" Version="4.11.0" />
|
||||||
<PackageReference Include="Npgsql" Version="8.0.5" />
|
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,14 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
result = _userLogic.FilterUserVehicles(result, GetUserID());
|
result = _userLogic.FilterUserVehicles(result, GetUserID());
|
||||||
}
|
}
|
||||||
return Json(result);
|
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
|
||||||
|
{
|
||||||
|
return Json(result, StaticHelper.GetInvariantOption());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace CarCareTracker.Controllers
|
|||||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
private readonly IReminderHelper _reminderHelper;
|
private readonly IReminderHelper _reminderHelper;
|
||||||
private readonly ITranslationHelper _translationHelper;
|
private readonly ITranslationHelper _translationHelper;
|
||||||
|
private readonly IMailHelper _mailHelper;
|
||||||
public HomeController(ILogger<HomeController> logger,
|
public HomeController(ILogger<HomeController> logger,
|
||||||
IVehicleDataAccess dataAccess,
|
IVehicleDataAccess dataAccess,
|
||||||
IUserLogic userLogic,
|
IUserLogic userLogic,
|
||||||
@@ -33,7 +34,8 @@ namespace CarCareTracker.Controllers
|
|||||||
IExtraFieldDataAccess extraFieldDataAccess,
|
IExtraFieldDataAccess extraFieldDataAccess,
|
||||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
IReminderHelper reminderHelper,
|
IReminderHelper reminderHelper,
|
||||||
ITranslationHelper translationHelper)
|
ITranslationHelper translationHelper,
|
||||||
|
IMailHelper mailHelper)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dataAccess = dataAccess;
|
_dataAccess = dataAccess;
|
||||||
@@ -46,6 +48,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_loginLogic = loginLogic;
|
_loginLogic = loginLogic;
|
||||||
_vehicleLogic = vehicleLogic;
|
_vehicleLogic = vehicleLogic;
|
||||||
_translationHelper = translationHelper;
|
_translationHelper = translationHelper;
|
||||||
|
_mailHelper = mailHelper;
|
||||||
}
|
}
|
||||||
private int GetUserID()
|
private int GetUserID()
|
||||||
{
|
{
|
||||||
@@ -555,6 +558,29 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return Json(false);
|
return Json(false);
|
||||||
}
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
public IActionResult GetServerConfiguration()
|
||||||
|
{
|
||||||
|
var viewModel = new ServerSettingsViewModel
|
||||||
|
{
|
||||||
|
PostgresConnection = _config.GetServerPostgresConnection(),
|
||||||
|
AllowedFileExtensions = _config.GetAllowedFileUploadExtensions(),
|
||||||
|
CustomLogoURL = _config.GetLogoUrl(),
|
||||||
|
MessageOfTheDay = _config.GetMOTD(),
|
||||||
|
WebHookURL = _config.GetWebHookUrl(),
|
||||||
|
CustomWidgetsEnabled = _config.GetCustomWidgetsEnabled(),
|
||||||
|
InvariantAPIEnabled = _config.GetInvariantApi(),
|
||||||
|
SMTPConfig = _config.GetMailConfig(),
|
||||||
|
OIDCConfig = _config.GetOpenIDConfig()
|
||||||
|
};
|
||||||
|
return PartialView("_ServerConfig", viewModel);
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
public IActionResult SendTestEmail(string emailAddress)
|
||||||
|
{
|
||||||
|
var result = _mailHelper.SendTestEmail(emailAddress);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
public IActionResult Error()
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,7 +136,15 @@ namespace CarCareTracker.Controllers
|
|||||||
//validate JWT token
|
//validate JWT token
|
||||||
var tokenParser = new JwtSecurityTokenHandler();
|
var tokenParser = new JwtSecurityTokenHandler();
|
||||||
var parsedToken = tokenParser.ReadJwtToken(userJwt);
|
var parsedToken = tokenParser.ReadJwtToken(userJwt);
|
||||||
var userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
var userEmailAddress = string.Empty;
|
||||||
|
if (parsedToken.Claims.Any(x => x.Type == "email"))
|
||||||
|
{
|
||||||
|
userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
var returnedClaims = parsedToken.Claims.Select(x => x.Type);
|
||||||
|
_logger.LogError($"OpenID Provider did not provide an email claim, claims returned: {string.Join(",", returnedClaims)}");
|
||||||
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(userEmailAddress))
|
if (!string.IsNullOrWhiteSpace(userEmailAddress))
|
||||||
{
|
{
|
||||||
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
|
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
|
||||||
@@ -180,6 +188,108 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return new RedirectResult("/Login");
|
return new RedirectResult("/Login");
|
||||||
}
|
}
|
||||||
|
public async Task<IActionResult> RemoteAuthDebug(string code, string state = "")
|
||||||
|
{
|
||||||
|
List<OperationResponse> results = new List<OperationResponse>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(code))
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Succeed($"Received code from OpenID Provider: {code}"));
|
||||||
|
//received code from OIDC provider
|
||||||
|
//create http client to retrieve user token from OIDC
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
var openIdConfig = _config.GetOpenIDConfig();
|
||||||
|
//check if validate state is enabled.
|
||||||
|
if (openIdConfig.ValidateState)
|
||||||
|
{
|
||||||
|
var storedStateValue = Request.Cookies["OIDC_STATE"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(storedStateValue))
|
||||||
|
{
|
||||||
|
Response.Cookies.Delete("OIDC_STATE");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(storedStateValue) || string.IsNullOrWhiteSpace(state) || storedStateValue != state)
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Failed($"Failed State Validation - Expected: {storedStateValue} Received: {state}"));
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Succeed($"Passed State Validation - Expected: {storedStateValue} Received: {state}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var httpParams = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("code", code),
|
||||||
|
new KeyValuePair<string, string>("grant_type", "authorization_code"),
|
||||||
|
new KeyValuePair<string, string>("client_id", openIdConfig.ClientId),
|
||||||
|
new KeyValuePair<string, string>("client_secret", openIdConfig.ClientSecret),
|
||||||
|
new KeyValuePair<string, string>("redirect_uri", openIdConfig.RedirectURL)
|
||||||
|
};
|
||||||
|
if (openIdConfig.UsePKCE)
|
||||||
|
{
|
||||||
|
//retrieve stored challenge verifier
|
||||||
|
var storedVerifier = Request.Cookies["OIDC_VERIFIER"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(storedVerifier))
|
||||||
|
{
|
||||||
|
httpParams.Add(new KeyValuePair<string, string>("code_verifier", storedVerifier));
|
||||||
|
Response.Cookies.Delete("OIDC_VERIFIER");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var httpRequest = new HttpRequestMessage(HttpMethod.Post, openIdConfig.TokenURL)
|
||||||
|
{
|
||||||
|
Content = new FormUrlEncodedContent(httpParams)
|
||||||
|
};
|
||||||
|
var tokenResult = await httpClient.SendAsync(httpRequest).Result.Content.ReadAsStringAsync();
|
||||||
|
var userJwt = JsonSerializer.Deserialize<OpenIDResult>(tokenResult)?.id_token ?? string.Empty;
|
||||||
|
if (!string.IsNullOrWhiteSpace(userJwt))
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Succeed($"Passed JWT Parsing - id_token: {userJwt}"));
|
||||||
|
//validate JWT token
|
||||||
|
var tokenParser = new JwtSecurityTokenHandler();
|
||||||
|
var parsedToken = tokenParser.ReadJwtToken(userJwt);
|
||||||
|
var userEmailAddress = string.Empty;
|
||||||
|
if (parsedToken.Claims.Any(x => x.Type == "email"))
|
||||||
|
{
|
||||||
|
userEmailAddress = parsedToken.Claims.First(x => x.Type == "email").Value;
|
||||||
|
results.Add(OperationResponse.Succeed($"Passed Claim Validation - email"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var returnedClaims = parsedToken.Claims.Select(x => x.Type);
|
||||||
|
results.Add(OperationResponse.Failed($"Failed Claim Validation - Expected: email Received: {string.Join(",", returnedClaims)}"));
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(userEmailAddress))
|
||||||
|
{
|
||||||
|
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = userEmailAddress });
|
||||||
|
if (userData.Id != default)
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Succeed($"Passed User Validation - Email: {userEmailAddress} Username: {userData.UserName}"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Succeed($"Passed Email Validation - Email: {userEmailAddress} User not registered"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Failed($"Failed Email Validation - No email received from OpenID Provider"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Failed($"Failed to parse JWT - Expected: id_token Received: {tokenResult}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Failed("No code received from OpenID Provider"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Failed($"Exception: {ex.Message}"));
|
||||||
|
}
|
||||||
|
return View(results);
|
||||||
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Login(LoginModel credentials)
|
public IActionResult Login(LoginModel credentials)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,8 +64,9 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId)
|
public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
var result = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
|
||||||
result.RemoveAll(x => !x.IsRecurring);
|
result.RemoveAll(x => !x.IsRecurring);
|
||||||
|
result = result.OrderByDescending(x => x.Urgency).ThenBy(x => x.Description).ToList();
|
||||||
return PartialView("_RecurringReminderSelector", result);
|
return PartialView("_RecurringReminderSelector", result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|||||||
12
Enum/ExtraFieldType.cs
Normal file
12
Enum/ExtraFieldType.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public enum ExtraFieldType
|
||||||
|
{
|
||||||
|
Text = 0,
|
||||||
|
Number = 1,
|
||||||
|
Decimal = 2,
|
||||||
|
Date = 3,
|
||||||
|
Time = 4,
|
||||||
|
Location = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -238,6 +238,7 @@ namespace CarCareTracker.Helper
|
|||||||
UserLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US"),
|
UserLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US"),
|
||||||
HideSoldVehicles = CheckBool(CheckString(nameof(UserConfig.HideSoldVehicles))),
|
HideSoldVehicles = CheckBool(CheckString(nameof(UserConfig.HideSoldVehicles))),
|
||||||
EnableShopSupplies = CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies))),
|
EnableShopSupplies = CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies))),
|
||||||
|
ShowCalendar = CheckBool(CheckString(nameof(UserConfig.ShowCalendar))),
|
||||||
EnableExtraFieldColumns = CheckBool(CheckString(nameof(UserConfig.EnableExtraFieldColumns))),
|
EnableExtraFieldColumns = CheckBool(CheckString(nameof(UserConfig.EnableExtraFieldColumns))),
|
||||||
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>() ?? new UserConfig().VisibleTabs,
|
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>() ?? new UserConfig().VisibleTabs,
|
||||||
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>() ?? new UserConfig().TabOrder,
|
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>() ?? new UserConfig().TabOrder,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace CarCareTracker.Helper
|
|||||||
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
||||||
OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token);
|
OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token);
|
||||||
OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders);
|
OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders);
|
||||||
|
OperationResponse SendTestEmail(string emailAddress);
|
||||||
}
|
}
|
||||||
public class MailHelper : IMailHelper
|
public class MailHelper : IMailHelper
|
||||||
{
|
{
|
||||||
@@ -74,6 +75,28 @@ namespace CarCareTracker.Helper
|
|||||||
return OperationResponse.Failed();
|
return OperationResponse.Failed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public OperationResponse SendTestEmail(string emailAddress)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
{
|
||||||
|
return OperationResponse.Failed("SMTP Server Not Setup");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(emailAddress))
|
||||||
|
{
|
||||||
|
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||||
|
}
|
||||||
|
string emailSubject = _translator.Translate(serverLanguage, "Test Email from LubeLogger");
|
||||||
|
string emailBody = _translator.Translate(serverLanguage, "If you are seeing this email it means your SMTP configuration is functioning correctly");
|
||||||
|
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return OperationResponse.Succeed("Email Sent!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return OperationResponse.Failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
public OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token)
|
public OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace CarCareTracker.Helper
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StaticHelper
|
public static class StaticHelper
|
||||||
{
|
{
|
||||||
public const string VersionNumber = "1.4.5";
|
public const string VersionNumber = "1.4.6";
|
||||||
public const string DbName = "data/cartracker.db";
|
public const string DbName = "data/cartracker.db";
|
||||||
public const string UserConfigPath = "data/config/userConfig.json";
|
public const string UserConfigPath = "data/config/userConfig.json";
|
||||||
public const string LegacyUserConfigPath = "config/userConfig.json";
|
public const string LegacyUserConfigPath = "config/userConfig.json";
|
||||||
@@ -262,7 +262,9 @@ namespace CarCareTracker.Helper
|
|||||||
//update isrequired setting
|
//update isrequired setting
|
||||||
foreach (ExtraField extraField in recordExtraFields)
|
foreach (ExtraField extraField in recordExtraFields)
|
||||||
{
|
{
|
||||||
extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired;
|
var firstMatchingField = templateExtraFields.First(x => x.Name == extraField.Name);
|
||||||
|
extraField.IsRequired = firstMatchingField.IsRequired;
|
||||||
|
extraField.FieldType = firstMatchingField.FieldType;
|
||||||
}
|
}
|
||||||
//append extra fields
|
//append extra fields
|
||||||
foreach (ExtraField extraField in templateExtraFields)
|
foreach (ExtraField extraField in templateExtraFields)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
public string AuthURL { get; set; }
|
public string AuthURL { get; set; }
|
||||||
public string TokenURL { get; set; }
|
public string TokenURL { get; set; }
|
||||||
public string RedirectURL { get; set; }
|
public string RedirectURL { get; set; }
|
||||||
public string Scope { get; set; }
|
public string Scope { get; set; } = "openid email";
|
||||||
public string State { get; set; }
|
public string State { get; set; }
|
||||||
public string CodeChallenge { get; set; }
|
public string CodeChallenge { get; set; }
|
||||||
public bool ValidateState { get; set; } = false;
|
public bool ValidateState { get; set; } = false;
|
||||||
|
|||||||
17
Models/Settings/ServerSettingsViewModel.cs
Normal file
17
Models/Settings/ServerSettingsViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ServerSettingsViewModel
|
||||||
|
{
|
||||||
|
public string LocaleInfo { get; set; }
|
||||||
|
public string PostgresConnection { get; set; }
|
||||||
|
public string AllowedFileExtensions { get; set; }
|
||||||
|
public string CustomLogoURL { get; set; }
|
||||||
|
public string MessageOfTheDay { get; set; }
|
||||||
|
public string WebHookURL { get; set; }
|
||||||
|
public bool CustomWidgetsEnabled { get; set; }
|
||||||
|
public bool InvariantAPIEnabled { get; set; }
|
||||||
|
public MailConfig SMTPConfig { get; set; } = new MailConfig();
|
||||||
|
public OpenIDConfig OIDCConfig { get; set; } = new OpenIDConfig();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
public bool IsRequired { get; set; }
|
public bool IsRequired { get; set; }
|
||||||
|
public ExtraFieldType FieldType { get; set; } = ExtraFieldType.Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
public string PreferredGasUnit { get; set; } = string.Empty;
|
public string PreferredGasUnit { get; set; } = string.Empty;
|
||||||
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
||||||
public bool UseUnitForFuelCost { get; set; }
|
public bool UseUnitForFuelCost { get; set; }
|
||||||
|
public bool ShowCalendar { get; set; }
|
||||||
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
|
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
|
||||||
public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig();
|
public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig();
|
||||||
public string UserNameHash { get; set; }
|
public string UserNameHash { get; set; }
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace CarCareTracker.Models
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Models
|
||||||
{
|
{
|
||||||
public class Vehicle
|
public class Vehicle
|
||||||
{
|
{
|
||||||
@@ -8,7 +10,9 @@
|
|||||||
public string Make { get; set; }
|
public string Make { get; set; }
|
||||||
public string Model { get; set; }
|
public string Model { get; set; }
|
||||||
public string LicensePlate { get; set; }
|
public string LicensePlate { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string PurchaseDate { get; set; }
|
public string PurchaseDate { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string SoldDate { get; set; }
|
public string SoldDate { get; set; }
|
||||||
public decimal PurchasePrice { get; set; }
|
public decimal PurchasePrice { get; set; }
|
||||||
public decimal SoldPrice { get; set; }
|
public decimal SoldPrice { get; set; }
|
||||||
@@ -22,10 +26,12 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Primarily used for vehicles with odometer units different from user's settings.
|
/// Primarily used for vehicles with odometer units different from user's settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(FromDecimalOptional))]
|
||||||
public string OdometerMultiplier { get; set; } = "1";
|
public string OdometerMultiplier { get; set; } = "1";
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Primarily used for vehicles where the odometer does not reflect actual mileage.
|
/// Primarily used for vehicles where the odometer does not reflect actual mileage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
public string OdometerDifference { get; set; } = "0";
|
public string OdometerDifference { get; set; } = "0";
|
||||||
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
|
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -27,9 +27,11 @@
|
|||||||
<button class="nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</span></button>
|
<button class="nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</span></button>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="nav-item" role="presentation">
|
@if(userConfig.ShowCalendar){
|
||||||
<button class="nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-calendar-week me-2"></i>@translator.Translate(userLanguage, "Calendar")</span></button>
|
<li class="nav-item" role="presentation">
|
||||||
</li>
|
<button class="nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-calendar-week me-2"></i>@translator.Translate(userLanguage, "Calendar")</span></button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</span></button>
|
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</span></button>
|
||||||
</li>
|
</li>
|
||||||
@@ -78,9 +80,11 @@
|
|||||||
<button class="nav-link resizable-nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Supplies")</span></button>
|
<button class="nav-link resizable-nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Supplies")</span></button>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="nav-item" role="presentation">
|
@if (userConfig.ShowCalendar){
|
||||||
<button class="nav-link resizable-nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-calendar-week"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Calendar")</span></button>
|
<li class="nav-item" role="presentation">
|
||||||
</li>
|
<button class="nav-link resizable-nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-calendar-week"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Calendar")</span></button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
<li class="nav-item ms-auto" role="presentation">
|
<li class="nav-item ms-auto" role="presentation">
|
||||||
<button class="nav-link resizable-nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Settings")</span></button>
|
<button class="nav-link resizable-nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Settings")</span></button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
@model KioskViewModel
|
@model KioskViewModel
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/lib/masonry/masonry.min.js"></script>
|
<script src="~/lib/masonry/masonry.min.js"></script>
|
||||||
|
<script src="~/lib/drawdown/drawdown.js"></script>
|
||||||
}
|
}
|
||||||
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
|
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
|
||||||
<div class="progress-bar" style="width: 0%"></div>
|
<div class="progress-bar" style="width: 0%"></div>
|
||||||
@@ -123,9 +124,15 @@
|
|||||||
}
|
}
|
||||||
function toggleReminderNote(sender){
|
function toggleReminderNote(sender){
|
||||||
var reminderNote = $(sender).find('.reminder-note');
|
var reminderNote = $(sender).find('.reminder-note');
|
||||||
if (reminderNote.text().trim() != ''){
|
var reminderNoteText = reminderNote.text().trim();
|
||||||
|
if (reminderNoteText != ''){
|
||||||
if (reminderNote.hasClass('d-none')) {
|
if (reminderNote.hasClass('d-none')) {
|
||||||
reminderNote.removeClass('d-none');
|
reminderNote.removeClass('d-none');
|
||||||
|
if (!reminderNote.hasClass('reminder-note-markdown')){
|
||||||
|
let markedDownReminderNote = markdown(reminderNoteText);
|
||||||
|
reminderNote.html(markedDownReminderNote);
|
||||||
|
reminderNote.addClass('reminder-note-markdown');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
reminderNote.addClass('d-none');
|
reminderNote.addClass('d-none');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,9 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-8">@translator.Translate(userLanguage, "Name")</th>
|
<th scope="col" class="col-5">@translator.Translate(userLanguage, "Name")</th>
|
||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Required")</th>
|
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Required")</th>
|
||||||
|
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Type")</th>
|
||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
|
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -47,11 +48,21 @@
|
|||||||
@foreach (ExtraField extraField in Model.ExtraFields)
|
@foreach (ExtraField extraField in Model.ExtraFields)
|
||||||
{
|
{
|
||||||
<script>
|
<script>
|
||||||
extraFields.push({ name: decodeHTMLEntities('@extraField.Name'), isRequired: @extraField.IsRequired.ToString().ToLower()});
|
extraFields.push({ name: decodeHTMLEntities('@extraField.Name'), isRequired: @extraField.IsRequired.ToString().ToLower(), fieldType: decodeHTMLEntities('@extraField.FieldType')});
|
||||||
</script>
|
</script>
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<td class="col-8">@extraField.Name</td>
|
<td class="col-5">@extraField.Name</td>
|
||||||
<td class="col-2"><input class="form-check-input" type="checkbox" onchange="updateExtraFieldIsRequired(decodeHTMLEntities('@extraField.Name'), this)" value="" @(extraField.IsRequired ? "checked" : "") /></td>
|
<td class="col-2"><input class="form-check-input" type="checkbox" onchange="updateExtraFieldIsRequired(decodeHTMLEntities('@extraField.Name'), this)" value="" @(extraField.IsRequired ? "checked" : "") /></td>
|
||||||
|
<td class="col-3">
|
||||||
|
<select class="form-select" onchange="updateExtraFieldType(decodeHTMLEntities('@extraField.Name'), this)">
|
||||||
|
<!option @(extraField.FieldType == ExtraFieldType.Text ? "selected" : "") value="@((int)ExtraFieldType.Text)">@translator.Translate(userLanguage, "Text")</!option>
|
||||||
|
<!option @(extraField.FieldType == ExtraFieldType.Number ? "selected" : "") value="@((int)ExtraFieldType.Number)">@translator.Translate(userLanguage, "Number")</!option>
|
||||||
|
<!option @(extraField.FieldType == ExtraFieldType.Decimal ? "selected" : "") value="@((int)ExtraFieldType.Decimal)">@translator.Translate(userLanguage, "Decimal")</!option>
|
||||||
|
<!option @(extraField.FieldType == ExtraFieldType.Date ? "selected" : "") value="@((int)ExtraFieldType.Date)">@translator.Translate(userLanguage, "Date")</!option>
|
||||||
|
<!option @(extraField.FieldType == ExtraFieldType.Time ? "selected" : "") value="@((int)ExtraFieldType.Time)">@translator.Translate(userLanguage, "Time")</!option>
|
||||||
|
<!option @(extraField.FieldType == ExtraFieldType.Location ? "selected" : "") value="@((int)ExtraFieldType.Location)">@translator.Translate(userLanguage, "Location")</!option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
<td class="col-2"><button type="button" onclick="deleteExtraField(decodeHTMLEntities('@extraField.Name'))" class="btn btn-danger"><i class="bi bi-trash"></i></button></td>
|
<td class="col-2"><button type="button" onclick="deleteExtraField(decodeHTMLEntities('@extraField.Name'))" class="btn btn-danger"><i class="bi bi-trash"></i></button></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -99,6 +110,12 @@
|
|||||||
extraFieldToEdit.isRequired = $(checkbox).is(":checked");
|
extraFieldToEdit.isRequired = $(checkbox).is(":checked");
|
||||||
updateExtraFields();
|
updateExtraFields();
|
||||||
}
|
}
|
||||||
|
function updateExtraFieldType(fieldId, dropDown){
|
||||||
|
var indexToEdit = extraFields.findIndex(x => x.name == fieldId);
|
||||||
|
var extraFieldToEdit = extraFields[indexToEdit];
|
||||||
|
extraFieldToEdit.fieldType = $(dropDown).val();
|
||||||
|
updateExtraFields();
|
||||||
|
}
|
||||||
function deleteExtraField(fieldId) {
|
function deleteExtraField(fieldId) {
|
||||||
extraFields = extraFields.filter(x => x.name != fieldId);
|
extraFields = extraFields.filter(x => x.name != fieldId);
|
||||||
updateExtraFields();
|
updateExtraFields();
|
||||||
|
|||||||
219
Views/Home/_ServerConfig.cshtml
Normal file
219
Views/Home/_ServerConfig.cshtml
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@model ServerSettingsViewModel
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="serverConfigModalLabel">@translator.Translate(userLanguage, "Review Server Configurations")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideServerConfigModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputPostgres">@translator.Translate(userLanguage, "Postgres Connection")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputPostgres" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.PostgresConnection">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputFileExt">@translator.Translate(userLanguage, "Allowed File Extensions")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputFileExt" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.AllowedFileExtensions">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputLogoURL">@translator.Translate(userLanguage, "Logo URL")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputLogoURL" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.CustomLogoURL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputMOTD">@translator.Translate(userLanguage, "Message of the Day")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputMOTD" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.MessageOfTheDay">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputWebHook">@translator.Translate(userLanguage, "WebHook URL")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputWebHook" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.WebHookURL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputCustomWidget">@translator.Translate(userLanguage, "Custom Widgets")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputCustomWidget" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@(Model.CustomWidgetsEnabled ? translator.Translate(userLanguage, "Enabled") : translator.Translate(userLanguage, "Disabled"))">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputInvariantAPI">@translator.Translate(userLanguage, "Invariant API")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputInvariantAPI" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@(Model.InvariantAPIEnabled ? translator.Translate(userLanguage, "Enabled") : translator.Translate(userLanguage, "Disabled"))">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputSMTPServer">@translator.Translate(userLanguage, "SMTP Server")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" readonly id="inputSMTPServer" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.EmailServer">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" @(string.IsNullOrWhiteSpace(Model.SMTPConfig.EmailServer) ? "disabled" : "") class="btn btn-sm text-secondary password-visible-button" onclick="sendTestEmail()"><i class="bi bi-send"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputSMTPPort">@translator.Translate(userLanguage, "SMTP Server Port")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputSMTPPort" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.Port">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputSMTPFrom">@translator.Translate(userLanguage, "SMTP Sender Address")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputSMTPFrom" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.EmailFrom">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputSMTPUsername">@translator.Translate(userLanguage, "SMTP Username")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputSMTPUsername" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.Username">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputSMTPPassword">@translator.Translate(userLanguage, "SMTP Password")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" readonly id="inputSMTPPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.Password">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCProvider">@translator.Translate(userLanguage, "OIDC Provider")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCProvider" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.Name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCClient">@translator.Translate(userLanguage, "OIDC Client ID")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCClient" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.ClientId">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCSecret">@translator.Translate(userLanguage, "OIDC Client Secret")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" readonly id="inputOIDCSecret" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.ClientSecret">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCAuth">@translator.Translate(userLanguage, "OIDC Auth URL")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCAuth" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.AuthURL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCToken">@translator.Translate(userLanguage, "OIDC Token URL")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.TokenURL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCRedirect">@translator.Translate(userLanguage, "OIDC Redirect URL")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCRedirect" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.RedirectURL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCScope">@translator.Translate(userLanguage, "OIDC Scope")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCScope" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.Scope">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCLogout">@translator.Translate(userLanguage, "OIDC Logout URL")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCLogout" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.LogOutURL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCState">@translator.Translate(userLanguage, "OIDC Validate State")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCState" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@(Model.OIDCConfig.ValidateState ? translator.Translate(userLanguage, "Enabled") : translator.Translate(userLanguage, "Disabled"))">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCPKCE">@translator.Translate(userLanguage, "OIDC Use PKCE")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCPKCE" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@(Model.OIDCConfig.UsePKCE ? translator.Translate(userLanguage, "Enabled") : translator.Translate(userLanguage, "Disabled"))">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="inputOIDCDisable">@translator.Translate(userLanguage, "OIDC Login Only")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCDisable" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@(Model.OIDCConfig.DisableRegularLogin ? translator.Translate(userLanguage, "Enabled") : translator.Translate(userLanguage, "Disabled"))">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -86,6 +86,10 @@
|
|||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
|
||||||
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
|
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="showCalendar" checked="@Model.UserConfig.ShowCalendar">
|
||||||
|
<label class="form-check-label" for="showCalendar">@translator.Translate(userLanguage, "Show Calendar")</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
{
|
{
|
||||||
@@ -253,7 +257,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<span class="lead text-wrap">@translator.Translate(userLanguage, "Server-wide Settings")</span>
|
<div class="row">
|
||||||
|
<div class="col-10">
|
||||||
|
<span class="lead text-wrap">@translator.Translate(userLanguage, "Server-wide Settings")</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button onclick="showServerConfigModal()" class="btn text-secondary btn-sm"><i class="bi bi-eyeglasses"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 d-grid">
|
<div class="col-6 d-grid">
|
||||||
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md text-truncate">@translator.Translate(userLanguage, "Extra Fields")</button>
|
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md text-truncate">@translator.Translate(userLanguage, "Extra Fields")</button>
|
||||||
@@ -355,6 +366,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="serverConfigModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content" id="serverConfigModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal fade" data-bs-focus="false" id="tabReorderModal" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="modal fade" data-bs-focus="false" id="tabReorderModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="tabReorderModalContent">
|
<div class="modal-content" id="tabReorderModalContent">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||||
<input type="text" id="inputUserName" class="form-control">
|
<input type="text" onkeyup="callBackOnEnter(event, requestPasswordReset)" id="inputUserName" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Request")</button>
|
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Request")</button>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" id="inputUserPassword" onkeyup="handlePasswordKeyPress(event)" class="form-control">
|
<input type="password" id="inputUserPassword" onkeyup="callBackOnEnter(event, performLogin)" class="form-control">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||||
<input type="text" id="inputUserName" class="form-control" value="@Model">
|
<input type="text" id="inputUserName" class="form-control" value="@Model" onkeyup="callBackOnEnter(event, performOpenIdRegistration)">
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="button" class="btn btn-warning mt-2" onclick="performOpenIdRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Register")</button>
|
<button type="button" class="btn btn-warning mt-2" onclick="performOpenIdRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Register")</button>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" id="inputUserPassword" class="form-control">
|
<input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performRegistration)">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
17
Views/Login/RemoteAuthDebug.cshtml
Normal file
17
Views/Login/RemoteAuthDebug.cshtml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@model List<OperationResponse>
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Remote Auth Debug";
|
||||||
|
}
|
||||||
|
<div class="mt-2">
|
||||||
|
@foreach (OperationResponse result in Model)
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert text-wrap text-break @(result.Success ? "alert-success" : "alert-danger")" role="alert">
|
||||||
|
@result.Message
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label>
|
<label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" id="inputUserPassword" class="form-control">
|
<input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performPasswordReset)">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<a onclick="showRecurringReminderSelector('collisionRecordDescription')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
<a onclick="showRecurringReminderSelector('collisionRecordDescription', 'collisionRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -52,14 +52,7 @@
|
|||||||
<!option value="@tag">@tag</!option>
|
<!option value="@tag">@tag</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="collisionRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="collisionRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
42
Views/Vehicle/_ExtraField.cshtml
Normal file
42
Views/Vehicle/_ExtraField.cshtml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
@model List<ExtraField>
|
||||||
|
@if (Model.Any()){
|
||||||
|
@foreach (ExtraField field in Model)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<div class="extra-field">
|
||||||
|
<label for="@elementId">@field.Name</label>
|
||||||
|
@switch(field.FieldType){
|
||||||
|
case (ExtraFieldType.Text):
|
||||||
|
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Number):
|
||||||
|
<input type="number" inputmode="numeric" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Decimal):
|
||||||
|
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Date):
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
|
</div>
|
||||||
|
<script>initExtraFieldDatePicker('@elementId')</script>
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Time):
|
||||||
|
<input type="time" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Location):
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="populateLocationField('@elementId')"><i class="bi bi-geo-alt"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,14 +72,7 @@
|
|||||||
<!option value="@tag">@tag</!option>
|
<!option value="@tag">@tag</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="odometerRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="odometerRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -40,14 +40,7 @@
|
|||||||
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
|
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
|
||||||
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
|
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (!isNew)
|
@if (!isNew)
|
||||||
{
|
{
|
||||||
<label>@($"{translator.Translate(userLanguage, "Date Created")}: {Model.DateCreated}")</label>
|
<label>@($"{translator.Translate(userLanguage, "Date Created")}: {Model.DateCreated}")</label>
|
||||||
|
|||||||
@@ -40,14 +40,7 @@
|
|||||||
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
|
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
|
||||||
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
|
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="planRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="planRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
var userLanguage = userConfig.UserLanguage;
|
var userLanguage = userConfig.UserLanguage;
|
||||||
}
|
}
|
||||||
@using CarCareTracker.Helper
|
@using CarCareTracker.Helper
|
||||||
@model List<ReminderRecord>
|
@model List<ReminderRecordViewModel>
|
||||||
@if (Model.Count() > 1)
|
@if (Model.Count() > 1)
|
||||||
{
|
{
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
@@ -16,9 +16,25 @@
|
|||||||
<select class="form-select" id="recurringReminderInput">
|
<select class="form-select" id="recurringReminderInput">
|
||||||
@if (Model.Any())
|
@if (Model.Any())
|
||||||
{
|
{
|
||||||
@foreach (ReminderRecord reminderRecord in Model)
|
@foreach (ReminderRecordViewModel reminderRecord in Model)
|
||||||
{
|
{
|
||||||
<!option value="@reminderRecord.Id">@reminderRecord.Description</!option>
|
@switch(reminderRecord.UserMetric){
|
||||||
|
case (ReminderMetric.Both):
|
||||||
|
<!option value="@reminderRecord.Id" data-description="@reminderRecord.Description" class="@StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
|
||||||
|
@($"{reminderRecord.Description} | {reminderRecord.Date.ToShortDateString()} | {reminderRecord.Mileage}")
|
||||||
|
</!option>
|
||||||
|
break;
|
||||||
|
case (ReminderMetric.Odometer):
|
||||||
|
<!option value="@reminderRecord.Id" data-description="@reminderRecord.Description" class="@StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
|
||||||
|
@($"{reminderRecord.Description} | {reminderRecord.Mileage}")
|
||||||
|
</!option>
|
||||||
|
break;
|
||||||
|
case (ReminderMetric.Date):
|
||||||
|
<!option value="@reminderRecord.Id" data-description="@reminderRecord.Description" class="@StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
|
||||||
|
@($"{reminderRecord.Description} | {reminderRecord.Date.ToShortDateString()}")
|
||||||
|
</!option>
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
@@ -27,11 +43,26 @@
|
|||||||
</select>
|
</select>
|
||||||
<div id="recurringMultipleReminders" style="display:none;">
|
<div id="recurringMultipleReminders" style="display:none;">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
@foreach (ReminderRecord reminderRecord in Model)
|
@foreach (ReminderRecordViewModel reminderRecord in Model)
|
||||||
{
|
{
|
||||||
<li class="list-group-item text-start">
|
<li class="list-group-item text-start">
|
||||||
<input class="form-check-input" type="checkbox" value="@reminderRecord.Id" id="recurringReminder_@reminderRecord.Id">
|
<input class="form-check-input" type="checkbox" value="@reminderRecord.Id" data-description="@reminderRecord.Description" id="recurringReminder_@reminderRecord.Id">
|
||||||
<label class="form-check-label stretched-link" for="recurringReminder_@reminderRecord.Id">@reminderRecord.Description</label>
|
<label class="form-check-label stretched-link" for="recurringReminder_@reminderRecord.Id">
|
||||||
|
@reminderRecord.Description
|
||||||
|
<br /><small class="badge @StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
|
||||||
|
@switch (reminderRecord.UserMetric){
|
||||||
|
case (ReminderMetric.Both):
|
||||||
|
<i class='bi bi-calendar-event me-2'></i>@reminderRecord.Date.ToShortDateString()<i class='bi bi-speedometer ms-2 me-2'></i>@reminderRecord.Mileage
|
||||||
|
break;
|
||||||
|
case (ReminderMetric.Odometer):
|
||||||
|
<i class='bi bi-speedometer me-2'></i>@reminderRecord.Mileage
|
||||||
|
break;
|
||||||
|
case (ReminderMetric.Date):
|
||||||
|
<i class='bi bi-calendar-event me-2'></i>@reminderRecord.Date.ToShortDateString()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -85,6 +85,20 @@
|
|||||||
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@if(hasRefresh){
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='done' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Done" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Done">@translator.Translate(userLanguage, "Done")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='delete' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Delete" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Delete">@translator.Translate(userLanguage, "Delete")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -113,9 +127,9 @@
|
|||||||
<th scope="col" data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@translator.Translate(userLanguage, "Notes")</th>
|
<th scope="col" data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@translator.Translate(userLanguage, "Notes")</th>
|
||||||
@if (hasRefresh)
|
@if (hasRefresh)
|
||||||
{
|
{
|
||||||
<th scope="col" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Done")</th>
|
<th scope="col" data-column="done" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Done")</th>
|
||||||
}
|
}
|
||||||
<th scope="col" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Delete")</th>
|
<th scope="col" data-column="delete" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Delete")</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -164,14 +178,14 @@
|
|||||||
<td data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
|
<td data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
|
||||||
@if (hasRefresh)
|
@if (hasRefresh)
|
||||||
{
|
{
|
||||||
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">
|
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint" data-column="done">
|
||||||
@if((reminderRecord.Urgency == ReminderUrgency.VeryUrgent || reminderRecord.Urgency == ReminderUrgency.PastDue) && reminderRecord.IsRecurring)
|
@if((reminderRecord.Urgency == ReminderUrgency.VeryUrgent || reminderRecord.Urgency == ReminderUrgency.PastDue) && reminderRecord.IsRecurring)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-secondary" onclick="markDoneReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-check-lg"></i></button>
|
<button type="button" class="btn btn-secondary" onclick="markDoneReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-check-lg"></i></button>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">
|
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint" data-column="delete">
|
||||||
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
|
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<a onclick="showRecurringReminderSelector('serviceRecordDescription')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
<a onclick="showRecurringReminderSelector('serviceRecordDescription', 'serviceRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -52,14 +52,7 @@
|
|||||||
<!option value="@tag">@tag</!option>
|
<!option value="@tag">@tag</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="serviceRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="serviceRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -50,14 +50,7 @@
|
|||||||
<!option value="@tag">@tag</!option>
|
<!option value="@tag">@tag</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="supplyRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="supplyRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<a onclick="showRecurringReminderSelector('taxRecordDescription')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
<a onclick="showRecurringReminderSelector('taxRecordDescription', 'taxRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -41,14 +41,7 @@
|
|||||||
<!option value="@tag">@tag</!option>
|
<!option value="@tag">@tag</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="taxRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="taxRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<a onclick="showRecurringReminderSelector('upgradeRecordDescription')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
<a onclick="showRecurringReminderSelector('upgradeRecordDescription', 'upgradeRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -52,14 +52,7 @@
|
|||||||
<!option value="@tag">@tag</!option>
|
<!option value="@tag">@tag</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="upgradeRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="upgradeRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -36,14 +36,7 @@
|
|||||||
<input type="text" id="inputModel" class="form-control" placeholder="@translator.Translate(userLanguage, "Model")" value="@Model.Model">
|
<input type="text" id="inputModel" class="form-control" placeholder="@translator.Translate(userLanguage, "Model")" value="@Model.Model">
|
||||||
<label for="inputLicensePlate">@translator.Translate(userLanguage, "License Plate")</label>
|
<label for="inputLicensePlate">@translator.Translate(userLanguage, "License Plate")</label>
|
||||||
<input type="text" id="inputLicensePlate" class="form-control" placeholder="@translator.Translate(userLanguage, "License Plate")" value="@Model.LicensePlate">
|
<input type="text" id="inputLicensePlate" class="form-control" placeholder="@translator.Translate(userLanguage, "License Plate")" value="@Model.LicensePlate">
|
||||||
@foreach (ExtraField field in Model.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<label for="inputIdentifier" class="@(!Model.ExtraFields.Any() ? "d-none" : "")">@translator.Translate(userLanguage, "Identifier")</label>
|
<label for="inputIdentifier" class="@(!Model.ExtraFields.Any() ? "d-none" : "")">@translator.Translate(userLanguage, "Identifier")</label>
|
||||||
<select class="form-select @(!Model.ExtraFields.Any() ? "d-none" : "")" id="inputIdentifier" )>
|
<select class="form-select @(!Model.ExtraFields.Any() ? "d-none" : "")" id="inputIdentifier" )>
|
||||||
<!option value="LicensePlate" @(Model.VehicleIdentifier == "LicensePlate" ? "selected" : "")>@translator.Translate(userLanguage, "License Plate")</!option>
|
<!option value="LicensePlate" @(Model.VehicleIdentifier == "LicensePlate" ? "selected" : "")>@translator.Translate(userLanguage, "License Plate")</!option>
|
||||||
@@ -89,9 +82,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="collapsePurchaseInfo" class="accordion-collapse collapse" data-bs-parent="#vehicleModalAccordion">
|
<div id="collapsePurchaseInfo" class="accordion-collapse collapse" data-bs-parent="#vehicleModalAccordion">
|
||||||
<label for="inputPurchaseDate">@translator.Translate(userLanguage, "Purchased Date(optional)")</label>
|
<label for="inputPurchaseDate">@translator.Translate(userLanguage, "Purchased Date(optional)")</label>
|
||||||
<input type="text" id="inputPurchaseDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Date")" value="@Model.PurchaseDate">
|
<div class="input-group">
|
||||||
|
<input type="text" id="inputPurchaseDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Date")" value="@Model.PurchaseDate">
|
||||||
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
|
</div>
|
||||||
<label for="inputSoldDate">@translator.Translate(userLanguage, "Sold Date(optional)")</label>
|
<label for="inputSoldDate">@translator.Translate(userLanguage, "Sold Date(optional)")</label>
|
||||||
<input type="text" id="inputSoldDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Date")" value="@Model.SoldDate">
|
<div class="input-group">
|
||||||
|
<input type="text" id="inputSoldDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Date")" value="@Model.SoldDate">
|
||||||
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
|
</div>
|
||||||
<label for="inputPurchasePrice">@translator.Translate(userLanguage, "Purchased Price(optional)")</label>
|
<label for="inputPurchasePrice">@translator.Translate(userLanguage, "Purchased Price(optional)")</label>
|
||||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="inputPurchasePrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Price")" value="@(Model.PurchasePrice == default ? "" : Model.PurchasePrice)">
|
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="inputPurchasePrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Price")" value="@(Model.PurchasePrice == default ? "" : Model.PurchasePrice)">
|
||||||
<label for="inputSoldPrice">@translator.Translate(userLanguage, "Sold Price(optional)")</label>
|
<label for="inputSoldPrice">@translator.Translate(userLanguage, "Sold Price(optional)")</label>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"EnableAutoReminderRefresh": false,
|
"EnableAutoReminderRefresh": false,
|
||||||
"EnableAutoOdometerInsert": false,
|
"EnableAutoOdometerInsert": false,
|
||||||
"EnableShopSupplies": false,
|
"EnableShopSupplies": false,
|
||||||
|
"ShowCalendar": true,
|
||||||
"EnableExtraFieldColumns": false,
|
"EnableExtraFieldColumns": false,
|
||||||
"UseUKMPG": false,
|
"UseUKMPG": false,
|
||||||
"UseThreeDecimalGasCost": true,
|
"UseThreeDecimalGasCost": true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="../favicon.ico">
|
||||||
<title>LubeLogger Configurator</title>
|
<title>LubeLogger Configurator</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||||
@@ -227,7 +227,9 @@
|
|||||||
<textarea id="outputModalText" readonly style="width:100%; height:450px;"></textarea>
|
<textarea id="outputModalText" readonly style="width:100%; height:450px;"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary btn-strip me-auto" onclick="removeDoubleQuotes()">Remove Double Quotes</button>
|
<button type="button" class="btn btn-secondary btn-strip me-auto" onclick="removeDoubleQuotes()">Remove Double Quotes</button>
|
||||||
|
<input id="appSettingsUpload" onChange="readUploadedFile()" class="d-none" type="file" accept="application/json">
|
||||||
|
<button type="button" class="btn btn-secondary btn-upload me-auto" onclick="uploadAndMerge()">Upload appsettings.json</button>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
<button type="button" class="btn btn-primary btn-copy" onclick="copyToClipboard()">Copy</button>
|
<button type="button" class="btn btn-primary btn-copy" onclick="copyToClipboard()">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -237,6 +239,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
|
function uploadAndMerge(){
|
||||||
|
$("#appSettingsUpload").click();
|
||||||
|
}
|
||||||
|
function readUploadedFile(){
|
||||||
|
let fl_files = $("#appSettingsUpload")[0].files; // JS FileList object
|
||||||
|
|
||||||
|
if (fl_files.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the 1st file from the list
|
||||||
|
let fl_file = fl_files[0];
|
||||||
|
|
||||||
|
let reader = new FileReader(); // built in API
|
||||||
|
|
||||||
|
let display_file = ( e ) => { // set the contents of the <textarea>
|
||||||
|
mergeIntoUploadedFile(e.target.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_reader_load = ( fl ) => {
|
||||||
|
return display_file; // a function
|
||||||
|
};
|
||||||
|
|
||||||
|
// Closure to capture the file information.
|
||||||
|
reader.onload = on_reader_load( fl_file );
|
||||||
|
|
||||||
|
// Read the file as text.
|
||||||
|
reader.readAsText( fl_file );
|
||||||
|
}
|
||||||
|
function mergeIntoUploadedFile(fileContents){
|
||||||
|
var newJsonObject = JSON.parse("{" + $("#outputModalText").text() + "}");
|
||||||
|
var currentJsonObject = JSON.parse(fileContents);
|
||||||
|
var mergedJsonObject = {...currentJsonObject, ...newJsonObject};
|
||||||
|
$("#outputModalLabel").text("Content for appsettings.json");
|
||||||
|
$("#outputModalText").text(JSON.stringify(mergedJsonObject, null, 2));
|
||||||
|
//clear out uploaded file content
|
||||||
|
$("#appSettingsUpload").val("");
|
||||||
|
}
|
||||||
function removeDoubleQuotes(){
|
function removeDoubleQuotes(){
|
||||||
var currentText = $("#outputModalText").text();
|
var currentText = $("#outputModalText").text();
|
||||||
$("#outputModalText").text(currentText.replaceAll('"', ''));
|
$("#outputModalText").text(currentText.replaceAll('"', ''));
|
||||||
@@ -319,6 +359,11 @@ function generateConfig(){
|
|||||||
$("#outputModalLabel").text("Append into appsettings.json");
|
$("#outputModalLabel").text("Append into appsettings.json");
|
||||||
$("#outputModalText").text(JSON.stringify(windowConfig, null, 2).slice(1,-1));
|
$("#outputModalText").text(JSON.stringify(windowConfig, null, 2).slice(1,-1));
|
||||||
$(".btn-strip").hide();
|
$(".btn-strip").hide();
|
||||||
|
if (jQuery.isEmptyObject(windowConfig)){
|
||||||
|
$(".btn-upload").hide();
|
||||||
|
} else {
|
||||||
|
$(".btn-upload").show();
|
||||||
|
}
|
||||||
$("#outputModal").modal("show");
|
$("#outputModal").modal("show");
|
||||||
} else {
|
} else {
|
||||||
var dockerConfig = [];
|
var dockerConfig = [];
|
||||||
@@ -375,6 +420,7 @@ function generateConfig(){
|
|||||||
$("#outputModalLabel").text("Content for .env");
|
$("#outputModalLabel").text("Content for .env");
|
||||||
$("#outputModalText").text(dockerConfig.join("\r\n"));
|
$("#outputModalText").text(dockerConfig.join("\r\n"));
|
||||||
$(".btn-strip").show();
|
$(".btn-strip").show();
|
||||||
|
$(".btn-upload").hide();
|
||||||
$("#outputModal").modal("show");
|
$("#outputModal").modal("show");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -323,10 +323,11 @@ function updateMPGLabels() {
|
|||||||
var maxMPG = rowMPG.length > 0 ? rowMPG.reduce((a, b) => a > b ? a : b) : 0;
|
var maxMPG = rowMPG.length > 0 ? rowMPG.reduce((a, b) => a > b ? a : b) : 0;
|
||||||
var minMPG = rowMPG.length > 0 && rowNonZeroMPG.length > 0 ? rowNonZeroMPG.reduce((a, b) => a < b ? a : b) : 0;
|
var minMPG = rowMPG.length > 0 && rowNonZeroMPG.length > 0 ? rowNonZeroMPG.reduce((a, b) => a < b ? a : b) : 0;
|
||||||
var totalMilesTraveled = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
|
var totalMilesTraveled = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
|
||||||
var totalGasConsumed = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
|
var totalGasConsumed = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
|
||||||
var totalUnaggregatedGasConsumed = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
|
var totalGasConsumedFV = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
|
||||||
|
var totalUnaggregatedGasConsumedFV = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
|
||||||
var totalMilesTraveledUnaggregated = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
|
var totalMilesTraveledUnaggregated = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
|
||||||
var fullGasConsumed = totalGasConsumed + totalUnaggregatedGasConsumed;
|
var fullGasConsumed = totalGasConsumedFV + totalUnaggregatedGasConsumedFV;
|
||||||
var fullDistanceTraveled = totalMilesTraveled + totalMilesTraveledUnaggregated;
|
var fullDistanceTraveled = totalMilesTraveled + totalMilesTraveledUnaggregated;
|
||||||
if (totalGasConsumed > 0 && rowNonZeroMPG.length > 0) {
|
if (totalGasConsumed > 0 && rowNonZeroMPG.length > 0) {
|
||||||
var averageMPG = totalMilesTraveled / totalGasConsumed;
|
var averageMPG = totalMilesTraveled / totalGasConsumed;
|
||||||
|
|||||||
@@ -55,12 +55,6 @@ function performPasswordReset() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePasswordKeyPress(event) {
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
performLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function remoteLogin() {
|
function remoteLogin() {
|
||||||
$.get('/Login/GetRemoteLoginLink', function (data) {
|
$.get('/Login/GetRemoteLoginLink', function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|||||||
@@ -4,6 +4,15 @@
|
|||||||
$("#extraFieldModal").modal('show');
|
$("#extraFieldModal").modal('show');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function showServerConfigModal() {
|
||||||
|
$.get(`/Home/GetServerConfiguration`, function (data) {
|
||||||
|
$("#serverConfigModalContent").html(data);
|
||||||
|
$("#serverConfigModal").modal('show');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function hideServerConfigModal() {
|
||||||
|
$("#serverConfigModal").modal('hide');
|
||||||
|
}
|
||||||
function hideExtraFieldModal() {
|
function hideExtraFieldModal() {
|
||||||
$("#extraFieldModal").modal('hide');
|
$("#extraFieldModal").modal('hide');
|
||||||
}
|
}
|
||||||
@@ -62,6 +71,7 @@ function updateSettings() {
|
|||||||
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
|
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
|
||||||
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
|
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
|
||||||
enableShopSupplies: $("#enableShopSupplies").is(":checked"),
|
enableShopSupplies: $("#enableShopSupplies").is(":checked"),
|
||||||
|
showCalendar: $("#showCalendar").is(":checked"),
|
||||||
enableExtraFieldColumns: $("#enableExtraFieldColumns").is(":checked"),
|
enableExtraFieldColumns: $("#enableExtraFieldColumns").is(":checked"),
|
||||||
hideSoldVehicles: $("#hideSoldVehicles").is(":checked"),
|
hideSoldVehicles: $("#hideSoldVehicles").is(":checked"),
|
||||||
preferredGasUnit: $("#preferredGasUnit").val(),
|
preferredGasUnit: $("#preferredGasUnit").val(),
|
||||||
@@ -85,6 +95,33 @@ function updateSettings() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function sendTestEmail() {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Send Test Email',
|
||||||
|
html: `
|
||||||
|
<input type="text" id="testEmailRecipient" class="swal2-input" placeholder="Email Address" onkeydown="handleSwalEnter(event)">
|
||||||
|
`,
|
||||||
|
confirmButtonText: 'Send',
|
||||||
|
focusConfirm: false,
|
||||||
|
preConfirm: () => {
|
||||||
|
const emailRecipient = $("#testEmailRecipient").val();
|
||||||
|
if (!emailRecipient || emailRecipient.trim() == '') {
|
||||||
|
Swal.showValidationMessage(`Please enter a valid email address`);
|
||||||
|
}
|
||||||
|
return { emailRecipient }
|
||||||
|
},
|
||||||
|
}).then(function (result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
$.post('/Home/SendTestEmail', { emailAddress: result.value.emailRecipient }, function (data) {
|
||||||
|
if (data.success) {
|
||||||
|
successToast(data.message);
|
||||||
|
} else {
|
||||||
|
errorToast(data.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
function makeBackup() {
|
function makeBackup() {
|
||||||
$.get('/Files/MakeBackup', function (data) {
|
$.get('/Files/MakeBackup', function (data) {
|
||||||
window.location.href = data;
|
window.location.href = data;
|
||||||
|
|||||||
@@ -336,6 +336,16 @@ function isValidMoney(input) {
|
|||||||
const usRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}((,\d{3}){0,8}|(\d{3}){0,8})(\.\d{1,3}?)?\)?$/;
|
const usRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}((,\d{3}){0,8}|(\d{3}){0,8})(\.\d{1,3}?)?\)?$/;
|
||||||
return (euRegex.test(input) || usRegex.test(input));
|
return (euRegex.test(input) || usRegex.test(input));
|
||||||
}
|
}
|
||||||
|
function initExtraFieldDatePicker(fieldName) {
|
||||||
|
let inputField = $(`#${fieldName}`);
|
||||||
|
if (inputField.length > 0) {
|
||||||
|
inputField.datepicker({
|
||||||
|
format: getShortDatePattern().pattern,
|
||||||
|
autoclose: true,
|
||||||
|
weekStart: getGlobalConfig().firstDayOfWeek
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
function initDatePicker(input, futureOnly) {
|
function initDatePicker(input, futureOnly) {
|
||||||
if (futureOnly) {
|
if (futureOnly) {
|
||||||
input.datepicker({
|
input.datepicker({
|
||||||
@@ -702,7 +712,7 @@ function getAndValidateExtraFields() {
|
|||||||
var extraFieldsVisible = $(".modal.fade.show").find(".extra-field");
|
var extraFieldsVisible = $(".modal.fade.show").find(".extra-field");
|
||||||
extraFieldsVisible.map((index, elem) => {
|
extraFieldsVisible.map((index, elem) => {
|
||||||
var extraFieldName = $(elem).children("label").text();
|
var extraFieldName = $(elem).children("label").text();
|
||||||
var extraFieldInput = $(elem).children("input");
|
var extraFieldInput = $(elem).find("input");
|
||||||
var extraFieldValue = extraFieldInput.val();
|
var extraFieldValue = extraFieldInput.val();
|
||||||
var extraFieldIsRequired = extraFieldInput.hasClass('extra-field-required');
|
var extraFieldIsRequired = extraFieldInput.hasClass('extra-field-required');
|
||||||
if (extraFieldIsRequired && extraFieldValue.trim() == '') {
|
if (extraFieldIsRequired && extraFieldValue.trim() == '') {
|
||||||
@@ -1535,4 +1545,35 @@ function handleTableColumnDragEnd(tabName) {
|
|||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function callBackOnEnter(event, callBack) {
|
||||||
|
if (event.keyCode == 13) {
|
||||||
|
callBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateLocationField(fieldName) {
|
||||||
|
let populateLocationFieldCallBack = (position) => {
|
||||||
|
$(`#${fieldName}`).val(`${position.coords.latitude},${position.coords.longitude}`)
|
||||||
|
};
|
||||||
|
let populateLocationFieldErrorCallBack = (errMsg) => {
|
||||||
|
if (errMsg && errMsg.code) {
|
||||||
|
switch (errMsg.code) {
|
||||||
|
case 1:
|
||||||
|
errorToast(errMsg.message);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
errorToast("Location Unavailable");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
try {
|
||||||
|
navigator.geolocation.getCurrentPosition(populateLocationFieldCallBack, populateLocationFieldErrorCallBack, { maximumAge: 1000, timeout: 4000, enableHighAccuracy: true });
|
||||||
|
} catch (err) {
|
||||||
|
errorToast('Location Services not Enabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ function moveRecord(recordId, source, dest) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function showRecurringReminderSelector(descriptionFieldName) {
|
function showRecurringReminderSelector(descriptionFieldName, noteFieldName) {
|
||||||
$.get(`/Vehicle/GetRecurringReminderRecordsByVehicleId?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
|
$.get(`/Vehicle/GetRecurringReminderRecordsByVehicleId?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
//prompt user to select a recurring reminder
|
//prompt user to select a recurring reminder
|
||||||
@@ -356,9 +356,16 @@ function showRecurringReminderSelector(descriptionFieldName) {
|
|||||||
}).then(function (result) {
|
}).then(function (result) {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
recurringReminderRecordId = result.value.selectedRecurringReminderData.ids;
|
recurringReminderRecordId = result.value.selectedRecurringReminderData.ids;
|
||||||
var descriptionField = $(`#${descriptionFieldName}`);
|
let descriptionField = $(`#${descriptionFieldName}`);
|
||||||
|
let noteField = $(`#${noteFieldName}`);
|
||||||
if (descriptionField.length > 0) {
|
if (descriptionField.length > 0) {
|
||||||
descriptionField.val(result.value.selectedRecurringReminderData.text);
|
let descriptionFieldText = result.value.selectedRecurringReminderData.text.join(', ');
|
||||||
|
descriptionField.val(descriptionFieldText);
|
||||||
|
}
|
||||||
|
if (noteField.length > 0 && result.value.selectedRecurringReminderData.text.length > 1) {
|
||||||
|
result.value.selectedRecurringReminderData.text.map(x => {
|
||||||
|
noteField.append(`- ${x}\r\n`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -583,7 +590,7 @@ function getAndValidateSelectedRecurringReminder() {
|
|||||||
$("#recurringMultipleReminders :checked").map(function () {
|
$("#recurringMultipleReminders :checked").map(function () {
|
||||||
selectedRecurringRemindersArray.push({
|
selectedRecurringRemindersArray.push({
|
||||||
value: this.value,
|
value: this.value,
|
||||||
text: $(this).parent().find('.form-check-label').text()
|
text: $(this).attr("data-description")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (selectedRecurringRemindersArray.length == 0) {
|
if (selectedRecurringRemindersArray.length == 0) {
|
||||||
@@ -596,13 +603,13 @@ function getAndValidateSelectedRecurringReminder() {
|
|||||||
return {
|
return {
|
||||||
hasError: false,
|
hasError: false,
|
||||||
ids: selectedRecurringRemindersArray.map(x=>x.value),
|
ids: selectedRecurringRemindersArray.map(x=>x.value),
|
||||||
text: selectedRecurringRemindersArray.map(x=>x.text).join(', ')
|
text: selectedRecurringRemindersArray.map(x=>x.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//validate single reminder
|
//validate single reminder
|
||||||
var selectedRecurringReminder = $("#recurringReminderInput").val();
|
var selectedRecurringReminder = $("#recurringReminderInput").val();
|
||||||
var selectedRecurringReminderText = $("#recurringReminderInput option:selected").text();
|
var selectedRecurringReminderText = $("#recurringReminderInput option:selected").attr("data-description");
|
||||||
if (!selectedRecurringReminder || parseInt(selectedRecurringReminder) == 0) {
|
if (!selectedRecurringReminder || parseInt(selectedRecurringReminder) == 0) {
|
||||||
return {
|
return {
|
||||||
hasError: true,
|
hasError: true,
|
||||||
@@ -613,42 +620,43 @@ function getAndValidateSelectedRecurringReminder() {
|
|||||||
return {
|
return {
|
||||||
hasError: false,
|
hasError: false,
|
||||||
ids: [selectedRecurringReminder],
|
ids: [selectedRecurringReminder],
|
||||||
text: selectedRecurringReminderText
|
text: [selectedRecurringReminderText]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getLastOdometerReadingAndIncrement(odometerFieldName) {
|
function getLastOdometerReadingAndIncrement(odometerFieldName) {
|
||||||
Swal.fire({
|
$.get(`/Vehicle/GetMaxMileage?vehicleId=${GetVehicleId().vehicleId}`, function (currentOdometer) {
|
||||||
title: 'Increment Last Reported Odometer Reading',
|
let additionalHtml = isNaN(currentOdometer) || currentOdometer == 0 ? '' : `<span>Current Odometer: ${currentOdometer}</span><br/>`;
|
||||||
html: `
|
Swal.fire({
|
||||||
|
title: 'Increment Last Reported Odometer Reading',
|
||||||
|
html: `${additionalHtml}
|
||||||
<input type="text" inputmode="decimal" id="inputOdometerIncrement" class="swal2-input" placeholder="Increment" onkeydown="handleSwalEnter(event)">
|
<input type="text" inputmode="decimal" id="inputOdometerIncrement" class="swal2-input" placeholder="Increment" onkeydown="handleSwalEnter(event)">
|
||||||
`,
|
`,
|
||||||
confirmButtonText: 'Add',
|
confirmButtonText: 'Add',
|
||||||
focusConfirm: false,
|
focusConfirm: false,
|
||||||
preConfirm: () => {
|
preConfirm: () => {
|
||||||
const odometerIncrement = parseInt(globalParseFloat($("#inputOdometerIncrement").val()));
|
const odometerIncrement = parseInt(globalParseFloat($("#inputOdometerIncrement").val()));
|
||||||
if (isNaN(odometerIncrement) || odometerIncrement <= 0) {
|
if (isNaN(odometerIncrement) || odometerIncrement < 0) {
|
||||||
Swal.showValidationMessage(`Please enter a non-zero amount to increment`);
|
Swal.showValidationMessage(`Please enter a positive amount to increment or 0 to use current odometer`);
|
||||||
}
|
}
|
||||||
return { odometerIncrement }
|
return { odometerIncrement }
|
||||||
},
|
},
|
||||||
}).then(function (result) {
|
}).then(function (result) {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
var amountToIncrement = result.value.odometerIncrement;
|
var amountToIncrement = result.value.odometerIncrement;
|
||||||
$.get(`/Vehicle/GetMaxMileage?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
|
var newAmount = currentOdometer + amountToIncrement;
|
||||||
var newAmount = data + amountToIncrement;
|
if (!isNaN(newAmount)) {
|
||||||
if (!isNaN(newAmount)) {
|
var odometerField = $(`#${odometerFieldName}`);
|
||||||
var odometerField = $(`#${odometerFieldName}`);
|
if (odometerField.length > 0) {
|
||||||
if (odometerField.length > 0) {
|
odometerField.val(newAmount);
|
||||||
odometerField.val(newAmount);
|
} else {
|
||||||
|
errorToast(genericErrorMessage());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errorToast(genericErrorMessage());
|
errorToast(genericErrorMessage());
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
errorToast(genericErrorMessage());
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
replace(rx_link, function(all, p1, p2, p3, p4, p5, p6) {
|
replace(rx_link, function(all, p1, p2, p3, p4, p5, p6) {
|
||||||
stash[--si] = p4
|
stash[--si] = p4
|
||||||
? p2
|
? p2
|
||||||
? '<img src="' + p4 + '" alt="' + p3 + '"/>'
|
? '<img style="max-width:100%;max-height:100%;object-fit:scale-down;" src="' + p4 + '" alt="' + p3 + '"/>'
|
||||||
: '<a href="' + p4 + '">' + unesc(highlight(p3)) + '</a>'
|
: '<a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" target="_blank" href="' + p4 + '">' + unesc(highlight(p3)) + '</a>'
|
||||||
: p6;
|
: p6;
|
||||||
return si + '\uf8ff';
|
return si + '\uf8ff';
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user