Compare commits
228 Commits
v1.4.1
...
Hargata/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29680cf0e9 | ||
|
|
723eb1a769 | ||
|
|
732a628c20 | ||
|
|
84d40edb0e | ||
|
|
9842f0e501 | ||
|
|
44c5f921a5 | ||
|
|
fae4aa31aa | ||
|
|
adb505c87c | ||
|
|
d21b0a9e29 | ||
|
|
15e4181aff | ||
|
|
3a4627ef02 | ||
|
|
d9e11273bd | ||
|
|
8e6f40a1b1 | ||
|
|
55f86ecb3f | ||
|
|
8eef1465cf | ||
|
|
fd0ffba7c4 | ||
|
|
afb710e6ad | ||
|
|
419b755e7a | ||
|
|
8cd5342c02 | ||
|
|
fa32ecdb5a | ||
|
|
d08726bd85 | ||
|
|
a87861069b | ||
|
|
b6d6a8765d | ||
|
|
f307c0933a | ||
|
|
9968ccb541 | ||
|
|
5d3746f168 | ||
|
|
df0c4eeca2 | ||
|
|
5b3c0aed72 | ||
|
|
040728b96d | ||
|
|
e8b7b3e4ba | ||
|
|
cf1f1a884b | ||
|
|
c470db0590 | ||
|
|
cb71650adf | ||
|
|
923d59af0a | ||
|
|
d68e1a3939 | ||
|
|
bf954a2946 | ||
|
|
d3f29be227 | ||
|
|
5afe88a33a | ||
|
|
6937eff576 | ||
|
|
6371cccc48 | ||
|
|
fc174160e0 | ||
|
|
876f99fd26 | ||
|
|
75c65b4681 | ||
|
|
76031d27d7 | ||
|
|
4079a93c3e | ||
|
|
d913ab2009 | ||
|
|
6bfbcc4374 | ||
|
|
73d9a7e6e9 | ||
|
|
3cbce8b584 | ||
|
|
e4fcb52b24 | ||
|
|
ae4f01ac9a | ||
|
|
46b3845b3e | ||
|
|
ffb126276f | ||
|
|
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 | ||
|
|
efa2bbf6cc | ||
|
|
66ed9ba699 | ||
|
|
e841e64f78 | ||
|
|
b0f46803c5 | ||
|
|
ac0bef5de0 | ||
|
|
08ea40a08e | ||
|
|
1d8e8059cd | ||
|
|
20b4396a4c | ||
|
|
4c39cb4d06 | ||
|
|
f58e6abd9f | ||
|
|
4f1e83a7d7 | ||
|
|
e39b38c2d5 | ||
|
|
b9ffa6ce91 | ||
|
|
76c6753785 | ||
|
|
54f5062377 | ||
|
|
7e60fd9e40 | ||
|
|
926947bae4 | ||
|
|
2652e47018 | ||
|
|
c1500b6ed0 | ||
|
|
125bc44d2e | ||
|
|
c317c0a058 | ||
|
|
beb9498399 | ||
|
|
4e74940684 | ||
|
|
c0f73080d2 | ||
|
|
cd0a35537b | ||
|
|
4d5020b607 | ||
|
|
7d8b7596ce | ||
|
|
08b5c9f25a | ||
|
|
82a4f8d57b | ||
|
|
538a79319e | ||
|
|
0555dcbc43 | ||
|
|
7b1f19ff9f | ||
|
|
9cac427eb9 | ||
|
|
1e5950028f | ||
|
|
7a7d343c3f | ||
|
|
ebf6388414 | ||
|
|
fa5426be53 | ||
|
|
4c60bb20c9 | ||
|
|
b02f3c8f8b | ||
|
|
d5f769e5a4 | ||
|
|
2afd1188eb | ||
|
|
1c6301242d | ||
|
|
06016727d9 | ||
|
|
1ee0c27ff9 | ||
|
|
7f03a630c6 | ||
|
|
2aa19f4f3b | ||
|
|
4be3c16adc | ||
|
|
f1f99a67dd | ||
|
|
a4f15ffe15 | ||
|
|
c11dad0cb3 | ||
|
|
2bf2469657 | ||
|
|
a23b02e962 | ||
|
|
905ee9bf27 | ||
|
|
e26869b30b | ||
|
|
90867792d9 | ||
|
|
1238802a75 | ||
|
|
8f7c50b88f | ||
|
|
df24434689 | ||
|
|
878748803d | ||
|
|
80bbf7f22f | ||
|
|
28b0ea565a | ||
|
|
07ccd1dea9 | ||
|
|
6a8fba535a | ||
|
|
f1b82ad0d3 | ||
|
|
0035bc09dd | ||
|
|
863c57d7fb | ||
|
|
00ccb68ce2 | ||
|
|
c2995dcd25 | ||
|
|
7b63366627 | ||
|
|
ababa6bf27 | ||
|
|
3f1f42d4f0 | ||
|
|
4d76bd6d36 | ||
|
|
ab34d1682c | ||
|
|
552b7a6356 | ||
|
|
f86acf673a | ||
|
|
c6f49bafca | ||
|
|
fdbb325611 | ||
|
|
8c557ced85 | ||
|
|
ad0f7de506 | ||
|
|
15ec9bb454 | ||
|
|
9fe7558cfe | ||
|
|
79633dbe1d | ||
|
|
f325306e20 | ||
|
|
4778656063 | ||
|
|
f7e00523c2 | ||
|
|
bf3df7230b | ||
|
|
c5182e0ed6 | ||
|
|
4081438cba | ||
|
|
b72fe2bf37 | ||
|
|
4d9c687709 | ||
|
|
3d4b970967 | ||
|
|
792f295c45 | ||
|
|
af28753558 | ||
|
|
24fb663599 | ||
|
|
140506c9c3 | ||
|
|
519f159c8c | ||
|
|
bb019cbcd9 | ||
|
|
84219627ff | ||
|
|
f218e878c6 | ||
|
|
520f47955b | ||
|
|
8110ee18f1 | ||
|
|
55bf817310 | ||
|
|
f2f55f8118 | ||
|
|
f48e7cd0d4 | ||
|
|
c82e0c8b9b | ||
|
|
c72877e16b | ||
|
|
ccc9076397 | ||
|
|
1c716ecf4a | ||
|
|
2cc471b944 | ||
|
|
a6c450109a | ||
|
|
e13707305b | ||
|
|
68bc0383f4 | ||
|
|
878f4f3687 | ||
|
|
48a721adda | ||
|
|
548e8da78c | ||
|
|
beb28214cf | ||
|
|
b4bb31491b | ||
|
|
0b5f007327 | ||
|
|
d31a4aeea3 | ||
|
|
97acd873eb | ||
|
|
5cf3538be3 | ||
|
|
a34921701d | ||
|
|
5fcf54de09 | ||
|
|
68372bf995 | ||
|
|
9f99ac1531 | ||
|
|
b7384fda6b | ||
|
|
8f4d610825 | ||
|
|
6bbf4b2575 | ||
|
|
32db884620 | ||
|
|
1b736b36f8 | ||
|
|
22dfe5eb04 | ||
|
|
8fd49e20d5 | ||
|
|
c791070126 | ||
|
|
807603fd4f | ||
|
|
a944e5e5ab | ||
|
|
33e916cac2 | ||
|
|
a99d71dc41 | ||
|
|
67d91f1a0d | ||
|
|
de2db7d24e | ||
|
|
b360f77ea0 | ||
|
|
e869528522 | ||
|
|
7c0317d232 | ||
|
|
0e49497da1 | ||
|
|
0380382c47 | ||
|
|
23201b8c86 | ||
|
|
5f357c9f84 | ||
|
|
244272621c | ||
|
|
5792b84880 | ||
|
|
8e644093f9 | ||
|
|
9421cb57ca |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -8,6 +8,8 @@ Ideally, the Issues tab should only consist of bug reports and feature requests
|
|||||||
## Feature Requests
|
## Feature Requests
|
||||||
Feature Requests are cool, but we do want to avoid bloat and scope/feature creep.
|
Feature Requests are cool, but we do want to avoid bloat and scope/feature creep.
|
||||||
|
|
||||||
|
Read [this](https://github.com/hargata/lubelog/wiki/Scope-and-Purpose) to better understand the scope and purpose of this project.
|
||||||
|
|
||||||
LubeLogger is a Vehicle Maintenance and Fuel Mileage Tracker.
|
LubeLogger is a Vehicle Maintenance and Fuel Mileage Tracker.
|
||||||
It is not and should not be used for the following:
|
It is not and should not be used for the following:
|
||||||
- Project Management Software(e.g.: Jira)
|
- Project Management Software(e.g.: Jira)
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Report a bug
|
about: Report a bug
|
||||||
title: ''
|
title: "[BUG]"
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Please make sure you have performed the following steps before opening a new bug
|
|||||||
|
|
||||||
**Platform**
|
**Platform**
|
||||||
- [ ] Docker Image
|
- [ ] Docker Image
|
||||||
- [ ] Windows Standalone Executable
|
- [ ] Windows/Linux Standalone Executable
|
||||||
|
|
||||||
**Browser Console Errors(F12)**
|
**Browser Console Errors(F12)**
|
||||||
<!-- Attach a screenshot or codeblock containing the browser console error -->
|
<!-- Attach a screenshot or codeblock containing the browser console error -->
|
||||||
|
|||||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: "[FEATURE REQUEST]"
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Checklist**
|
||||||
|
Please make sure you have performed the following steps before submitting a new feature request, change `[ ]` to `[x]` to mark it as done
|
||||||
|
|
||||||
|
- [ ] I have read the [Contributing Guidelines](https://github.com/hargata/lubelog/blob/main/.github/CONTRIBUTING.md) and [Scope and Purpose](https://github.com/hargata/lubelog/wiki/Scope-and-Purpose)
|
||||||
|
- [ ] I have searched through existing issues.
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
<!-- Describe the feature request below this line -->
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,15 +1,6 @@
|
|||||||
.vs/
|
.vs/
|
||||||
bin/
|
bin/
|
||||||
obj/
|
obj/
|
||||||
wwwroot/images/
|
data/
|
||||||
cartracker.db
|
|
||||||
data/cartracker.db
|
|
||||||
wwwroot/documents/
|
|
||||||
wwwroot/temp/
|
|
||||||
wwwroot/imports/
|
|
||||||
wwwroot/translations/
|
|
||||||
config/userConfig.json
|
|
||||||
CarCareTracker.csproj.user
|
CarCareTracker.csproj.user
|
||||||
Properties/launchSettings.json
|
Properties/launchSettings.json
|
||||||
data/cartracker-log.db
|
|
||||||
data/widgets.html
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
public IActionResult Unauthorized()
|
public IActionResult Unauthorized()
|
||||||
{
|
{
|
||||||
if (!User.IsInRole("CookieAuth"))
|
if (User.IsInRole("APIAuth"))
|
||||||
{
|
{
|
||||||
Response.StatusCode = 403;
|
Response.StatusCode = 403;
|
||||||
return new EmptyResult();
|
return new EmptyResult();
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ namespace CarCareTracker.Controllers
|
|||||||
private string UploadFile(IFormFile fileToUpload)
|
private string UploadFile(IFormFile fileToUpload)
|
||||||
{
|
{
|
||||||
string uploadDirectory = "temp/";
|
string uploadDirectory = "temp/";
|
||||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
|
||||||
if (!Directory.Exists(uploadPath))
|
if (!Directory.Exists(uploadPath))
|
||||||
Directory.CreateDirectory(uploadPath);
|
Directory.CreateDirectory(uploadPath);
|
||||||
string fileName = Guid.NewGuid() + Path.GetExtension(fileToUpload.FileName);
|
string fileName = Guid.NewGuid() + Path.GetExtension(fileToUpload.FileName);
|
||||||
@@ -95,7 +95,7 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult UploadCoordinates(List<string> coordinates)
|
public IActionResult UploadCoordinates(List<string> coordinates)
|
||||||
{
|
{
|
||||||
string uploadDirectory = "temp/";
|
string uploadDirectory = "temp/";
|
||||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
|
||||||
if (!Directory.Exists(uploadPath))
|
if (!Directory.Exists(uploadPath))
|
||||||
Directory.CreateDirectory(uploadPath);
|
Directory.CreateDirectory(uploadPath);
|
||||||
string fileName = Guid.NewGuid() + ".csv";
|
string fileName = Guid.NewGuid() + ".csv";
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
@@ -98,7 +101,6 @@ namespace CarCareTracker.Controllers
|
|||||||
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
|
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
|
||||||
return PartialView("_KioskPlan", kioskResult);
|
return PartialView("_KioskPlan", kioskResult);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case KioskMode.Reminder:
|
case KioskMode.Reminder:
|
||||||
{
|
{
|
||||||
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);
|
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);
|
||||||
@@ -556,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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,21 +49,31 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return View(model: redirectURL);
|
return View(model: redirectURL);
|
||||||
}
|
}
|
||||||
public IActionResult Registration()
|
public IActionResult Registration(string token = "", string email = "")
|
||||||
{
|
{
|
||||||
if (_config.GetServerDisabledRegistration())
|
if (_config.GetServerDisabledRegistration())
|
||||||
{
|
{
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
return View();
|
var viewModel = new LoginModel
|
||||||
|
{
|
||||||
|
EmailAddress = string.IsNullOrWhiteSpace(email) ? string.Empty : email,
|
||||||
|
Token = string.IsNullOrWhiteSpace(token) ? string.Empty : token
|
||||||
|
};
|
||||||
|
return View(viewModel);
|
||||||
}
|
}
|
||||||
public IActionResult ForgotPassword()
|
public IActionResult ForgotPassword()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
public IActionResult ResetPassword()
|
public IActionResult ResetPassword(string token = "", string email = "")
|
||||||
{
|
{
|
||||||
return View();
|
var viewModel = new LoginModel
|
||||||
|
{
|
||||||
|
EmailAddress = string.IsNullOrWhiteSpace(email) ? string.Empty : email,
|
||||||
|
Token = string.IsNullOrWhiteSpace(token) ? string.Empty : token
|
||||||
|
};
|
||||||
|
return View(viewModel);
|
||||||
}
|
}
|
||||||
public IActionResult GetRemoteLoginLink()
|
public IActionResult GetRemoteLoginLink()
|
||||||
{
|
{
|
||||||
@@ -130,13 +140,39 @@ namespace CarCareTracker.Controllers
|
|||||||
Content = new FormUrlEncodedContent(httpParams)
|
Content = new FormUrlEncodedContent(httpParams)
|
||||||
};
|
};
|
||||||
var tokenResult = await httpClient.SendAsync(httpRequest).Result.Content.ReadAsStringAsync();
|
var tokenResult = await httpClient.SendAsync(httpRequest).Result.Content.ReadAsStringAsync();
|
||||||
var userJwt = JsonSerializer.Deserialize<OpenIDResult>(tokenResult)?.id_token ?? string.Empty;
|
var decodedToken = JsonSerializer.Deserialize<OpenIDResult>(tokenResult);
|
||||||
|
var userJwt = decodedToken?.id_token ?? string.Empty;
|
||||||
|
var userAccessToken = decodedToken?.access_token ?? string.Empty;
|
||||||
if (!string.IsNullOrWhiteSpace(userJwt))
|
if (!string.IsNullOrWhiteSpace(userJwt))
|
||||||
{
|
{
|
||||||
//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 if (!string.IsNullOrWhiteSpace(openIdConfig.UserInfoURL) && !string.IsNullOrWhiteSpace(userAccessToken))
|
||||||
|
{
|
||||||
|
//retrieve claims from userinfo endpoint if no email claims are returned within id_token
|
||||||
|
var userInfoHttpRequest = new HttpRequestMessage(HttpMethod.Get, openIdConfig.UserInfoURL);
|
||||||
|
userInfoHttpRequest.Headers.Add("Authorization", $"Bearer {userAccessToken}");
|
||||||
|
var userInfoResult = await httpClient.SendAsync(userInfoHttpRequest).Result.Content.ReadAsStringAsync();
|
||||||
|
var userInfo = JsonSerializer.Deserialize<OpenIDUserInfo>(userInfoResult);
|
||||||
|
if (!string.IsNullOrWhiteSpace(userInfo?.email ?? string.Empty))
|
||||||
|
{
|
||||||
|
userEmailAddress = userInfo?.email ?? string.Empty;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
_logger.LogError($"OpenID Provider did not provide an email claim via UserInfo endpoint");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 +216,126 @@ 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 decodedToken = JsonSerializer.Deserialize<OpenIDResult>(tokenResult);
|
||||||
|
var userJwt = decodedToken?.id_token ?? string.Empty;
|
||||||
|
var userAccessToken = decodedToken?.access_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 if (!string.IsNullOrWhiteSpace(openIdConfig.UserInfoURL) && !string.IsNullOrWhiteSpace(userAccessToken))
|
||||||
|
{
|
||||||
|
//retrieve claims from userinfo endpoint if no email claims are returned within id_token
|
||||||
|
var userInfoHttpRequest = new HttpRequestMessage(HttpMethod.Get, openIdConfig.UserInfoURL);
|
||||||
|
userInfoHttpRequest.Headers.Add("Authorization", $"Bearer {userAccessToken}");
|
||||||
|
var userInfoResult = await httpClient.SendAsync(userInfoHttpRequest).Result.Content.ReadAsStringAsync();
|
||||||
|
var userInfo = JsonSerializer.Deserialize<OpenIDUserInfo>(userInfoResult);
|
||||||
|
if (!string.IsNullOrWhiteSpace(userInfo?.email ?? string.Empty))
|
||||||
|
{
|
||||||
|
userEmailAddress = userInfo?.email ?? string.Empty;
|
||||||
|
results.Add(OperationResponse.Succeed($"Passed Claim Validation - Retrieved email via UserInfo endpoint"));
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
results.Add(OperationResponse.Failed($"Failed Claim Validation - Unable to retrieve email via UserInfo endpoint: {openIdConfig.UserInfoURL} using access_token: {userAccessToken} - Received {userInfoResult}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
@@ -240,6 +396,12 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
public IActionResult SendRegistrationToken(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.SendRegistrationToken(credentials);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
public IActionResult RequestResetPassword(LoginModel credentials)
|
public IActionResult RequestResetPassword(LoginModel credentials)
|
||||||
{
|
{
|
||||||
var result = _loginLogic.RequestResetPassword(credentials);
|
var result = _loginLogic.RequestResetPassword(credentials);
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
|
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), gasRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
@@ -49,10 +54,11 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
|
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), gasRecord.VehicleId, User.Identity.Name, $"{(gasRecord.Id == default ? "Created" : "Edited")} Gas Record - Mileage: {gasRecord.Mileage.ToString()}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(gasRecord.ToGasRecord(), gasRecord.Id == default ? "gasrecord.add" : "gasrecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetAddGasRecordPartialView(int vehicleId)
|
public IActionResult GetAddGasRecordPartialView(int vehicleId)
|
||||||
{
|
{
|
||||||
@@ -65,6 +71,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetGasRecordForEditById(int gasRecordId)
|
public IActionResult GetGasRecordForEditById(int gasRecordId)
|
||||||
{
|
{
|
||||||
var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
|
var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
var convertedResult = new GasRecordInput
|
var convertedResult = new GasRecordInput
|
||||||
{
|
{
|
||||||
Id = result.Id,
|
Id = result.Id,
|
||||||
@@ -100,16 +111,16 @@ namespace CarCareTracker.Controllers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
|
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteGasRecordById(int gasRecordId)
|
public IActionResult DeleteGasRecordById(int gasRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteGasRecordWithChecks(gasRecordId);
|
var result = DeleteGasRecordWithChecks(gasRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|||||||
@@ -16,6 +16,176 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
return PartialView("_BulkDataImporter", mode);
|
return PartialView("_BulkDataImporter", mode);
|
||||||
}
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GenerateCsvSample(ImportMode mode)
|
||||||
|
{
|
||||||
|
string uploadDirectory = "temp/";
|
||||||
|
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
|
||||||
|
if (!Directory.Exists(uploadPath))
|
||||||
|
Directory.CreateDirectory(uploadPath);
|
||||||
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case ImportMode.ServiceRecord:
|
||||||
|
case ImportMode.RepairRecord:
|
||||||
|
case ImportMode.UpgradeRecord:
|
||||||
|
{
|
||||||
|
var exportData = new List<GenericRecordExportModel> { new GenericRecordExportModel
|
||||||
|
{
|
||||||
|
Date = DateTime.Now.ToShortDateString(),
|
||||||
|
Description = "Test",
|
||||||
|
Cost = 123.45M.ToString("C"),
|
||||||
|
Notes = "Test Note",
|
||||||
|
Odometer = 12345.ToString(),
|
||||||
|
Tags = "test1 test2"
|
||||||
|
} };
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
//custom writer
|
||||||
|
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.GasRecord:
|
||||||
|
{
|
||||||
|
var exportData = new List<GasRecordExportModel> { new GasRecordExportModel
|
||||||
|
{
|
||||||
|
Date = DateTime.Now.ToShortDateString(),
|
||||||
|
Odometer = 12345.ToString(),
|
||||||
|
FuelConsumed = 12.34M.ToString(),
|
||||||
|
Cost = 45.67M.ToString("C"),
|
||||||
|
IsFillToFull = true.ToString(),
|
||||||
|
MissedFuelUp = false.ToString(),
|
||||||
|
Notes = "Test Note",
|
||||||
|
Tags = "test1 test2"
|
||||||
|
} };
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
//custom writer
|
||||||
|
StaticHelper.WriteGasRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.OdometerRecord:
|
||||||
|
{
|
||||||
|
var exportData = new List<OdometerRecordExportModel> { new OdometerRecordExportModel
|
||||||
|
{
|
||||||
|
Date = DateTime.Now.ToShortDateString(),
|
||||||
|
InitialOdometer = 12345.ToString(),
|
||||||
|
Odometer = 12345.ToString(),
|
||||||
|
Notes = "Test Note",
|
||||||
|
Tags = "test1 test2"
|
||||||
|
} };
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
//custom writer
|
||||||
|
StaticHelper.WriteOdometerRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.TaxRecord:
|
||||||
|
{
|
||||||
|
var exportData = new List<TaxRecordExportModel> { new TaxRecordExportModel
|
||||||
|
{
|
||||||
|
Date = DateTime.Now.ToShortDateString(),
|
||||||
|
Description = "Test",
|
||||||
|
Cost = 123.45M.ToString("C"),
|
||||||
|
Notes = "Test Note",
|
||||||
|
Tags = "test1 test2"
|
||||||
|
} };
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
//custom writer
|
||||||
|
StaticHelper.WriteTaxRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.SupplyRecord:
|
||||||
|
{
|
||||||
|
var exportData = new List<SupplyRecordExportModel> { new SupplyRecordExportModel
|
||||||
|
{
|
||||||
|
Date = DateTime.Now.ToShortDateString(),
|
||||||
|
PartNumber = "TEST-123456",
|
||||||
|
PartSupplier = "Test Supplier",
|
||||||
|
PartQuantity = 1.5M.ToString(),
|
||||||
|
Description = "Test",
|
||||||
|
Cost = 123.45M.ToString("C"),
|
||||||
|
Notes = "Test Note",
|
||||||
|
Tags = "test1 test2"
|
||||||
|
} };
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
//custom writer
|
||||||
|
StaticHelper.WriteSupplyRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.PlanRecord:
|
||||||
|
{
|
||||||
|
var exportData = new List<PlanRecordExportModel> { new PlanRecordExportModel
|
||||||
|
{
|
||||||
|
DateCreated = DateTime.Now.ToString(),
|
||||||
|
DateModified = DateTime.Now.ToString(),
|
||||||
|
Description = "Test",
|
||||||
|
Type = ImportMode.RepairRecord.ToString(),
|
||||||
|
Priority = PlanPriority.Normal.ToString(),
|
||||||
|
Progress = PlanProgress.Testing.ToString(),
|
||||||
|
Cost = 123.45M.ToString("C"),
|
||||||
|
Notes = "Test Note"
|
||||||
|
} };
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
//custom writer
|
||||||
|
StaticHelper.WritePlanRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Json(OperationResponse.Failed("No parameters"));
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileBytes = _fileHelper.GetFileBytes(fullExportFilePath, true);
|
||||||
|
if (fileBytes.Length > 0)
|
||||||
|
{
|
||||||
|
return File(fileBytes, "text/csv", $"{mode.ToString().ToLower()}sample.csv");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Json(OperationResponse.Failed("An error has occurred while generating CSV sample: file has zero bytes"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return Json(OperationResponse.Failed($"An error has occurred while generating CSV sample: {ex.Message}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
|
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
|
||||||
@@ -25,7 +195,7 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(false);
|
return Json(false);
|
||||||
}
|
}
|
||||||
string uploadDirectory = "temp/";
|
string uploadDirectory = "temp/";
|
||||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
|
||||||
if (!Directory.Exists(uploadPath))
|
if (!Directory.Exists(uploadPath))
|
||||||
Directory.CreateDirectory(uploadPath);
|
Directory.CreateDirectory(uploadPath);
|
||||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
|||||||
@@ -26,11 +26,17 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveNoteToVehicleId(Note note)
|
public IActionResult SaveNoteToVehicleId(Note note)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), note.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
bool isCreate = note.Id == default; //needed here since Notes don't use an input object.
|
||||||
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), note.VehicleId, User.Identity.Name, $"{(note.Id == default ? "Created" : "Edited")} Note - Description: {note.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(note, isCreate ? "noterecord.add" : "noterecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -43,6 +49,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetNoteForEditById(int noteId)
|
public IActionResult GetNoteForEditById(int noteId)
|
||||||
{
|
{
|
||||||
var result = _noteDataAccess.GetNoteById(noteId);
|
var result = _noteDataAccess.GetNoteById(noteId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
return PartialView("_NoteModal", result);
|
return PartialView("_NoteModal", result);
|
||||||
}
|
}
|
||||||
private bool DeleteNoteWithChecks(int noteId)
|
private bool DeleteNoteWithChecks(int noteId)
|
||||||
@@ -54,16 +65,16 @@ namespace CarCareTracker.Controllers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var result = _noteDataAccess.DeleteNoteById(existingRecord.Id);
|
var result = _noteDataAccess.DeleteNoteById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(existingRecord, "noterecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteNoteById(int noteId)
|
public IActionResult DeleteNoteById(int noteId)
|
||||||
{
|
{
|
||||||
var result = DeleteNoteWithChecks(noteId);
|
var result = DeleteNoteWithChecks(noteId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|||||||
@@ -39,15 +39,21 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord)
|
public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), odometerRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
//move files from temp.
|
//move files from temp.
|
||||||
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
|
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), odometerRecord.VehicleId, User.Identity.Name, $"{(odometerRecord.Id == default ? "Created" : "Edited")} Odometer Record - Mileage: {odometerRecord.Mileage.ToString()}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord.ToOdometerRecord(), odometerRecord.Id == default ? "odometerrecord.add" : "odometerrecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
|
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
|
||||||
{
|
{
|
||||||
@@ -125,6 +131,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
|
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
|
||||||
{
|
{
|
||||||
var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
|
var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
//convert to Input object.
|
//convert to Input object.
|
||||||
var convertedResult = new OdometerRecordInput
|
var convertedResult = new OdometerRecordInput
|
||||||
{
|
{
|
||||||
@@ -149,16 +160,16 @@ namespace CarCareTracker.Controllers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
|
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
|
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteOdometerRecordWithChecks(odometerRecordId);
|
var result = DeleteOdometerRecordWithChecks(odometerRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
|
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
//populate createdDate
|
//populate createdDate
|
||||||
if (planRecord.Id == default)
|
if (planRecord.Id == default)
|
||||||
{
|
{
|
||||||
@@ -35,18 +40,23 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (planRecord.DeletedRequisitionHistory.Any())
|
if (planRecord.DeletedRequisitionHistory.Any())
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
|
||||||
}
|
}
|
||||||
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
|
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), planRecord.VehicleId, User.Identity.Name, $"{(planRecord.Id == default ? "Created" : "Edited")} Plan Record - Description: {planRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(planRecord.ToPlanRecord(), planRecord.Id == default ? "planrecord.add" : "planrecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord)
|
public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(OperationResponse.Failed("Access Denied"));
|
||||||
|
}
|
||||||
//check if template name already taken.
|
//check if template name already taken.
|
||||||
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
|
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
|
||||||
if (planRecord.Id == default && existingRecord)
|
if (planRecord.Id == default && existingRecord)
|
||||||
@@ -67,6 +77,16 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId)
|
public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId)
|
||||||
{
|
{
|
||||||
|
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||||
|
if (existingRecord.Id == default)
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
|
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -78,6 +98,11 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
return Json(OperationResponse.Failed("Unable to find template"));
|
return Json(OperationResponse.Failed("Unable to find template"));
|
||||||
}
|
}
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(OperationResponse.Failed("Access Denied"));
|
||||||
|
}
|
||||||
if (existingRecord.Supplies.Any())
|
if (existingRecord.Supplies.Any())
|
||||||
{
|
{
|
||||||
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
||||||
@@ -96,6 +121,11 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
return Json(OperationResponse.Failed("Unable to find template"));
|
return Json(OperationResponse.Failed("Unable to find template"));
|
||||||
}
|
}
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(OperationResponse.Failed("Access Denied"));
|
||||||
|
}
|
||||||
if (existingRecord.Supplies.Any())
|
if (existingRecord.Supplies.Any())
|
||||||
{
|
{
|
||||||
//check if all supplies are available
|
//check if all supplies are available
|
||||||
@@ -156,6 +186,11 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(false);
|
return Json(false);
|
||||||
}
|
}
|
||||||
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
existingRecord.Progress = planProgress;
|
existingRecord.Progress = planProgress;
|
||||||
existingRecord.DateModified = DateTime.Now;
|
existingRecord.DateModified = DateTime.Now;
|
||||||
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
|
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
|
||||||
@@ -239,6 +274,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetPlanRecordForEditById(int planRecordId)
|
public IActionResult GetPlanRecordForEditById(int planRecordId)
|
||||||
{
|
{
|
||||||
var result = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
var result = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
//convert to Input object.
|
//convert to Input object.
|
||||||
var convertedResult = new PlanRecordInput
|
var convertedResult = new PlanRecordInput
|
||||||
{
|
{
|
||||||
@@ -271,12 +311,12 @@ namespace CarCareTracker.Controllers
|
|||||||
//restore any requisitioned supplies if it has not been converted to other record types.
|
//restore any requisitioned supplies if it has not been converted to other record types.
|
||||||
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
|
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||||
}
|
}
|
||||||
var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id);
|
var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(existingRecord, "planrecord.delete", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,13 @@ namespace CarCareTracker.Controllers
|
|||||||
result = result.OrderByDescending(x => x.Urgency).ToList();
|
result = result.OrderByDescending(x => x.Urgency).ToList();
|
||||||
return PartialView("_ReminderRecords", result);
|
return PartialView("_ReminderRecords", result);
|
||||||
}
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[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]
|
||||||
@@ -105,10 +107,15 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
|
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), reminderRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
|
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), reminderRecord.VehicleId, User.Identity.Name, $"{(reminderRecord.Id == default ? "Created" : "Edited")} Reminder - Description: {reminderRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord.ToReminderRecord(), reminderRecord.Id == default ? "reminderrecord.add" : "reminderrecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -128,6 +135,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetReminderRecordForEditById(int reminderRecordId)
|
public IActionResult GetReminderRecordForEditById(int reminderRecordId)
|
||||||
{
|
{
|
||||||
var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
|
var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
//convert to Input object.
|
//convert to Input object.
|
||||||
var convertedResult = new ReminderRecordInput
|
var convertedResult = new ReminderRecordInput
|
||||||
{
|
{
|
||||||
@@ -145,6 +157,7 @@ namespace CarCareTracker.Controllers
|
|||||||
ReminderMonthInterval = result.ReminderMonthInterval,
|
ReminderMonthInterval = result.ReminderMonthInterval,
|
||||||
CustomMileageInterval = result.CustomMileageInterval,
|
CustomMileageInterval = result.CustomMileageInterval,
|
||||||
CustomMonthInterval = result.CustomMonthInterval,
|
CustomMonthInterval = result.CustomMonthInterval,
|
||||||
|
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
|
||||||
Tags = result.Tags
|
Tags = result.Tags
|
||||||
};
|
};
|
||||||
return PartialView("_ReminderRecordModal", convertedResult);
|
return PartialView("_ReminderRecordModal", convertedResult);
|
||||||
@@ -158,16 +171,16 @@ namespace CarCareTracker.Controllers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
|
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteReminderRecordById(int reminderRecordId)
|
public IActionResult DeleteReminderRecordById(int reminderRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteReminderRecordWithChecks(reminderRecordId);
|
var result = DeleteReminderRecordWithChecks(reminderRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord)
|
public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), collisionRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (collisionRecord.DeletedRequisitionHistory.Any())
|
if (collisionRecord.DeletedRequisitionHistory.Any())
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
|
||||||
}
|
}
|
||||||
//push back any reminders
|
//push back any reminders
|
||||||
if (collisionRecord.ReminderRecordId.Any())
|
if (collisionRecord.ReminderRecordId.Any())
|
||||||
@@ -61,7 +66,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
|
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), collisionRecord.VehicleId, User.Identity.Name, $"{(collisionRecord.Id == default ? "Created" : "Edited")} Repair Record - Description: {collisionRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(collisionRecord.ToCollisionRecord(), collisionRecord.Id == default ? "repairrecord.add" : "repairrecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -74,6 +79,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
|
public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
|
||||||
{
|
{
|
||||||
var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
|
var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
//convert to Input object.
|
//convert to Input object.
|
||||||
var convertedResult = new CollisionRecordInput
|
var convertedResult = new CollisionRecordInput
|
||||||
{
|
{
|
||||||
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
|
|||||||
//restore any requisitioned supplies.
|
//restore any requisitioned supplies.
|
||||||
if (existingRecord.RequisitionHistory.Any())
|
if (existingRecord.RequisitionHistory.Any())
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||||
}
|
}
|
||||||
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id);
|
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "repairrecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
|
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteCollisionRecordWithChecks(collisionRecordId);
|
var result = DeleteCollisionRecordWithChecks(collisionRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,15 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
//get records
|
//get records
|
||||||
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
|
||||||
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
var serviceRecords = vehicleRecords.ServiceRecords;
|
||||||
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
var gasRecords = vehicleRecords.GasRecords;
|
||||||
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
var collisionRecords = vehicleRecords.CollisionRecords;
|
||||||
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
var taxRecords = vehicleRecords.TaxRecords;
|
||||||
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
var upgradeRecords = vehicleRecords.UpgradeRecords;
|
||||||
|
var odometerRecords = vehicleRecords.OdometerRecords;
|
||||||
var userConfig = _config.GetUserConfig(User);
|
var userConfig = _config.GetUserConfig(User);
|
||||||
var viewModel = new ReportViewModel();
|
var viewModel = new ReportViewModel() { ReportHeaderForVehicle = new ReportHeader() };
|
||||||
//check if custom widgets are configured
|
//check if custom widgets are configured
|
||||||
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
|
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
|
||||||
//get totalCostMakeUp
|
//get totalCostMakeUp
|
||||||
@@ -91,6 +92,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
|
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||||
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
|
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
|
var averageMPG = _gasHelper.GetAverageGasMileage(mileageData, userConfig.UseMPG);
|
||||||
mileageData.RemoveAll(x => x.MilesPerGallon == default);
|
mileageData.RemoveAll(x => x.MilesPerGallon == default);
|
||||||
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
|
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
|
||||||
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
|
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
|
||||||
@@ -114,6 +116,12 @@ namespace CarCareTracker.Controllers
|
|||||||
monthMileage.Cost = 100 / monthMileage.Cost;
|
monthMileage.Cost = 100 / monthMileage.Cost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var newAverageMPG = decimal.Parse(averageMPG, NumberStyles.Any);
|
||||||
|
if (newAverageMPG != 0)
|
||||||
|
{
|
||||||
|
newAverageMPG = 100 / newAverageMPG;
|
||||||
|
}
|
||||||
|
averageMPG = newAverageMPG.ToString("F");
|
||||||
}
|
}
|
||||||
var mpgViewModel = new MPGForVehicleByMonth {
|
var mpgViewModel = new MPGForVehicleByMonth {
|
||||||
CostData = monthlyMileageData,
|
CostData = monthlyMileageData,
|
||||||
@@ -121,6 +129,15 @@ namespace CarCareTracker.Controllers
|
|||||||
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
|
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
|
||||||
};
|
};
|
||||||
viewModel.FuelMileageForVehicleByMonth = mpgViewModel;
|
viewModel.FuelMileageForVehicleByMonth = mpgViewModel;
|
||||||
|
//report header
|
||||||
|
|
||||||
|
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
|
||||||
|
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
|
||||||
|
|
||||||
|
viewModel.ReportHeaderForVehicle.TotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords);
|
||||||
|
viewModel.ReportHeaderForVehicle.AverageMPG = $"{averageMPG} {mpgViewModel.Unit}";
|
||||||
|
viewModel.ReportHeaderForVehicle.MaxOdometer = maxMileage;
|
||||||
|
viewModel.ReportHeaderForVehicle.DistanceTraveled = maxMileage - minMileage;
|
||||||
return PartialView("_Report", viewModel);
|
return PartialView("_Report", viewModel);
|
||||||
}
|
}
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
@@ -145,6 +162,63 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetSummaryForVehicle(int vehicleId, int year = 0)
|
||||||
|
{
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
|
||||||
|
|
||||||
|
var serviceRecords = vehicleRecords.ServiceRecords;
|
||||||
|
var gasRecords = vehicleRecords.GasRecords;
|
||||||
|
var collisionRecords = vehicleRecords.CollisionRecords;
|
||||||
|
var taxRecords = vehicleRecords.TaxRecords;
|
||||||
|
var upgradeRecords = vehicleRecords.UpgradeRecords;
|
||||||
|
var odometerRecords = vehicleRecords.OdometerRecords;
|
||||||
|
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
serviceRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
gasRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
collisionRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
taxRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
upgradeRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
odometerRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
|
||||||
|
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
|
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||||
|
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
|
var averageMPG = _gasHelper.GetAverageGasMileage(mileageData, userConfig.UseMPG);
|
||||||
|
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
|
||||||
|
|
||||||
|
if (invertedFuelMileageUnit)
|
||||||
|
{
|
||||||
|
var newAverageMPG = decimal.Parse(averageMPG, NumberStyles.Any);
|
||||||
|
if (newAverageMPG != 0)
|
||||||
|
{
|
||||||
|
newAverageMPG = 100 / newAverageMPG;
|
||||||
|
}
|
||||||
|
averageMPG = newAverageMPG.ToString("F");
|
||||||
|
}
|
||||||
|
|
||||||
|
var mpgUnit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit;
|
||||||
|
|
||||||
|
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
|
||||||
|
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
|
||||||
|
|
||||||
|
var viewModel = new ReportHeader()
|
||||||
|
{
|
||||||
|
TotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords),
|
||||||
|
AverageMPG = $"{averageMPG} {mpgUnit}",
|
||||||
|
MaxOdometer = maxMileage,
|
||||||
|
DistanceTraveled = maxMileage - minMileage
|
||||||
|
};
|
||||||
|
|
||||||
|
return PartialView("_ReportHeader", viewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetCostMakeUpForVehicle(int vehicleId, int year = 0)
|
public IActionResult GetCostMakeUpForVehicle(int vehicleId, int year = 0)
|
||||||
{
|
{
|
||||||
@@ -196,7 +270,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
var userConfig = _config.GetUserConfig(User);
|
var userConfig = _config.GetUserConfig(User);
|
||||||
var totalDistanceTraveled = maxMileage - minMileage;
|
var totalDistanceTraveled = maxMileage - minMileage;
|
||||||
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
|
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, year, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
|
||||||
var viewModel = new CostTableForVehicle
|
var viewModel = new CostTableForVehicle
|
||||||
{
|
{
|
||||||
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
||||||
@@ -349,9 +423,57 @@ namespace CarCareTracker.Controllers
|
|||||||
var vehicleHistory = new VehicleHistoryViewModel();
|
var vehicleHistory = new VehicleHistoryViewModel();
|
||||||
vehicleHistory.ReportParameters = reportParameter;
|
vehicleHistory.ReportParameters = reportParameter;
|
||||||
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
|
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleId);
|
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
|
||||||
|
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||||
|
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
||||||
|
var gasViewModels = _gasHelper.GetGasRecordViewModels(vehicleRecords.GasRecords, useMPG, useUKMPG);
|
||||||
|
//filter by tags
|
||||||
|
if (reportParameter.Tags.Any())
|
||||||
|
{
|
||||||
|
if (reportParameter.TagFilter == TagFilter.Exclude)
|
||||||
|
{
|
||||||
|
vehicleRecords.OdometerRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.ServiceRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.CollisionRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.TaxRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
gasViewModels.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.GasRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
}
|
||||||
|
else if (reportParameter.TagFilter == TagFilter.IncludeOnly)
|
||||||
|
{
|
||||||
|
vehicleRecords.OdometerRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.ServiceRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.CollisionRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.UpgradeRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.TaxRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
gasViewModels.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
vehicleRecords.GasRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//filter by date range.
|
||||||
|
if (reportParameter.FilterByDateRange && !string.IsNullOrWhiteSpace(reportParameter.StartDate) && !string.IsNullOrWhiteSpace(reportParameter.EndDate))
|
||||||
|
{
|
||||||
|
var startDate = DateTime.Parse(reportParameter.StartDate).Date;
|
||||||
|
var endDate = DateTime.Parse(reportParameter.EndDate).Date;
|
||||||
|
//validate date range
|
||||||
|
if (endDate >= startDate) //allow for same day.
|
||||||
|
{
|
||||||
|
vehicleHistory.StartDate = reportParameter.StartDate;
|
||||||
|
vehicleHistory.EndDate = reportParameter.EndDate;
|
||||||
|
//remove all records with dates after the end date and dates before the start date.
|
||||||
|
vehicleRecords.OdometerRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||||
|
vehicleRecords.ServiceRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||||
|
vehicleRecords.CollisionRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||||
|
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||||
|
vehicleRecords.TaxRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||||
|
gasViewModels.RemoveAll(x => DateTime.Parse(x.Date).Date > endDate || DateTime.Parse(x.Date).Date < startDate);
|
||||||
|
vehicleRecords.GasRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
|
||||||
vehicleHistory.Odometer = maxMileage.ToString("N0");
|
vehicleHistory.Odometer = maxMileage.ToString("N0");
|
||||||
var minMileage = _vehicleLogic.GetMinMileage(vehicleId);
|
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
|
||||||
var distanceTraveled = maxMileage - minMileage;
|
var distanceTraveled = maxMileage - minMileage;
|
||||||
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
|
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
|
||||||
{
|
{
|
||||||
@@ -388,17 +510,10 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<GenericReportModel> reportData = new List<GenericReportModel>();
|
List<GenericReportModel> reportData = new List<GenericReportModel>();
|
||||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
|
||||||
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
|
||||||
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
|
||||||
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
|
||||||
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
|
||||||
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
|
||||||
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
|
||||||
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||||
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
|
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
|
||||||
vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost);
|
vehicleHistory.TotalGasCost = gasViewModels.Sum(x => x.Cost);
|
||||||
vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost);
|
vehicleHistory.TotalCost = vehicleRecords.ServiceRecords.Sum(x => x.Cost) + vehicleRecords.CollisionRecords.Sum(x => x.Cost) + vehicleRecords.UpgradeRecords.Sum(x => x.Cost) + vehicleRecords.TaxRecords.Sum(x => x.Cost);
|
||||||
if (distanceTraveled != default)
|
if (distanceTraveled != default)
|
||||||
{
|
{
|
||||||
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0");
|
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0");
|
||||||
@@ -406,7 +521,6 @@ namespace CarCareTracker.Controllers
|
|||||||
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
|
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
|
||||||
}
|
}
|
||||||
var averageMPG = "0";
|
var averageMPG = "0";
|
||||||
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
|
|
||||||
if (gasViewModels.Any())
|
if (gasViewModels.Any())
|
||||||
{
|
{
|
||||||
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
|
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
|
||||||
@@ -425,7 +539,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}";
|
vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}";
|
||||||
//insert servicerecords
|
//insert servicerecords
|
||||||
reportData.AddRange(serviceRecords.Select(x => new GenericReportModel
|
reportData.AddRange(vehicleRecords.ServiceRecords.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
@@ -433,10 +547,11 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = x.Notes,
|
Notes = x.Notes,
|
||||||
Cost = x.Cost,
|
Cost = x.Cost,
|
||||||
DataType = ImportMode.ServiceRecord,
|
DataType = ImportMode.ServiceRecord,
|
||||||
ExtraFields = x.ExtraFields
|
ExtraFields = x.ExtraFields,
|
||||||
|
RequisitionHistory = x.RequisitionHistory
|
||||||
}));
|
}));
|
||||||
//repair records
|
//repair records
|
||||||
reportData.AddRange(repairRecords.Select(x => new GenericReportModel
|
reportData.AddRange(vehicleRecords.CollisionRecords.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
@@ -444,9 +559,10 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = x.Notes,
|
Notes = x.Notes,
|
||||||
Cost = x.Cost,
|
Cost = x.Cost,
|
||||||
DataType = ImportMode.RepairRecord,
|
DataType = ImportMode.RepairRecord,
|
||||||
ExtraFields = x.ExtraFields
|
ExtraFields = x.ExtraFields,
|
||||||
|
RequisitionHistory = x.RequisitionHistory
|
||||||
}));
|
}));
|
||||||
reportData.AddRange(upgradeRecords.Select(x => new GenericReportModel
|
reportData.AddRange(vehicleRecords.UpgradeRecords.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
@@ -454,9 +570,10 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = x.Notes,
|
Notes = x.Notes,
|
||||||
Cost = x.Cost,
|
Cost = x.Cost,
|
||||||
DataType = ImportMode.UpgradeRecord,
|
DataType = ImportMode.UpgradeRecord,
|
||||||
ExtraFields = x.ExtraFields
|
ExtraFields = x.ExtraFields,
|
||||||
|
RequisitionHistory = x.RequisitionHistory
|
||||||
}));
|
}));
|
||||||
reportData.AddRange(taxRecords.Select(x => new GenericReportModel
|
reportData.AddRange(vehicleRecords.TaxRecords.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = 0,
|
Odometer = 0,
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
|
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), serviceRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (serviceRecord.DeletedRequisitionHistory.Any())
|
if (serviceRecord.DeletedRequisitionHistory.Any())
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
|
||||||
}
|
}
|
||||||
//push back any reminders
|
//push back any reminders
|
||||||
if (serviceRecord.ReminderRecordId.Any())
|
if (serviceRecord.ReminderRecordId.Any())
|
||||||
@@ -61,7 +66,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
|
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), serviceRecord.VehicleId, User.Identity.Name, $"{(serviceRecord.Id == default ? "Created" : "Edited")} Service Record - Description: {serviceRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(serviceRecord.ToServiceRecord(), serviceRecord.Id == default ? "servicerecord.add" : "servicerecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -74,6 +79,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetServiceRecordForEditById(int serviceRecordId)
|
public IActionResult GetServiceRecordForEditById(int serviceRecordId)
|
||||||
{
|
{
|
||||||
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
|
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
//convert to Input object.
|
//convert to Input object.
|
||||||
var convertedResult = new ServiceRecordInput
|
var convertedResult = new ServiceRecordInput
|
||||||
{
|
{
|
||||||
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
|
|||||||
//restore any requisitioned supplies.
|
//restore any requisitioned supplies.
|
||||||
if (existingRecord.RequisitionHistory.Any())
|
if (existingRecord.RequisitionHistory.Any())
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||||
}
|
}
|
||||||
var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id);
|
var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "servicerecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteServiceRecordById(int serviceRecordId)
|
public IActionResult DeleteServiceRecordById(int serviceRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteServiceRecordWithChecks(serviceRecordId);
|
var result = DeleteServiceRecordWithChecks(serviceRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,44 +73,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
private void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription)
|
|
||||||
{
|
|
||||||
foreach (SupplyUsageHistory supply in supplyUsage)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (supply.Id == default)
|
|
||||||
{
|
|
||||||
continue; //no id, skip current supply.
|
|
||||||
}
|
|
||||||
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.Id);
|
|
||||||
if (result != null && result.Id != default)
|
|
||||||
{
|
|
||||||
//supply exists, re-add the quantity and cost
|
|
||||||
result.Quantity += supply.Quantity;
|
|
||||||
result.Cost += supply.Cost;
|
|
||||||
var requisitionRecord = new SupplyUsageHistory
|
|
||||||
{
|
|
||||||
Id = supply.Id,
|
|
||||||
Date = DateTime.Now.Date,
|
|
||||||
Description = $"Restored from {usageDescription}",
|
|
||||||
Quantity = supply.Quantity,
|
|
||||||
Cost = supply.Cost
|
|
||||||
};
|
|
||||||
result.RequisitionHistory.Add(requisitionRecord);
|
|
||||||
//save
|
|
||||||
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogError($"Unable to find supply with id {supply.Id}");
|
|
||||||
}
|
|
||||||
} catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError($"Error restoring supply with id {supply.Id} : {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
|
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
|
||||||
@@ -187,7 +150,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
|
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), supplyRecord.VehicleId, User.Identity.Name, $"{(supplyRecord.Id == default ? "Created" : "Edited")} Supply Record - Description: {supplyRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(supplyRecord.ToSupplyRecord(), supplyRecord.Id == default ? "supplyrecord.add" : "supplyrecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -236,16 +199,16 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id);
|
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(existingRecord, "supplyrecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
|
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteSupplyRecordWithChecks(supplyRecordId);
|
var result = DeleteSupplyRecordWithChecks(supplyRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,49 +23,29 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return PartialView("_TaxRecords", result);
|
return PartialView("_TaxRecords", result);
|
||||||
}
|
}
|
||||||
private void UpdateRecurringTaxes(int vehicleId)
|
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult CheckRecurringTaxRecords(int vehicleId)
|
||||||
{
|
{
|
||||||
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
try
|
||||||
var recurringFees = result.Where(x => x.IsRecurring);
|
|
||||||
if (recurringFees.Any())
|
|
||||||
{
|
{
|
||||||
foreach (TaxRecord recurringFee in recurringFees)
|
var result = _vehicleLogic.UpdateRecurringTaxes(vehicleId);
|
||||||
{
|
return Json(result);
|
||||||
var newDate = new DateTime();
|
} catch (Exception ex)
|
||||||
if (recurringFee.RecurringInterval != ReminderMonthInterval.Other)
|
{
|
||||||
{
|
_logger.LogError(ex.Message);
|
||||||
newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval);
|
return Json(false);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newDate = recurringFee.Date.AddMonths(recurringFee.CustomMonthInterval);
|
|
||||||
}
|
|
||||||
if (DateTime.Now > newDate)
|
|
||||||
{
|
|
||||||
recurringFee.IsRecurring = false;
|
|
||||||
var newRecurringFee = new TaxRecord()
|
|
||||||
{
|
|
||||||
VehicleId = recurringFee.VehicleId,
|
|
||||||
Date = newDate,
|
|
||||||
Description = recurringFee.Description,
|
|
||||||
Cost = recurringFee.Cost,
|
|
||||||
IsRecurring = true,
|
|
||||||
Notes = recurringFee.Notes,
|
|
||||||
RecurringInterval = recurringFee.RecurringInterval,
|
|
||||||
CustomMonthInterval = recurringFee.CustomMonthInterval,
|
|
||||||
Files = recurringFee.Files,
|
|
||||||
Tags = recurringFee.Tags,
|
|
||||||
ExtraFields = recurringFee.ExtraFields
|
|
||||||
};
|
|
||||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
|
||||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(newRecurringFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
|
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), taxRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
//move files from temp.
|
//move files from temp.
|
||||||
taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
//push back any reminders
|
//push back any reminders
|
||||||
@@ -77,9 +57,10 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
|
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
|
||||||
|
_vehicleLogic.UpdateRecurringTaxes(taxRecord.VehicleId);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), taxRecord.VehicleId, User.Identity.Name, $"{(taxRecord.Id == default ? "Created" : "Edited")} Tax Record - Description: {taxRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(taxRecord.ToTaxRecord(), taxRecord.Id == default ? "taxrecord.add" : "taxrecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -92,6 +73,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetTaxRecordForEditById(int taxRecordId)
|
public IActionResult GetTaxRecordForEditById(int taxRecordId)
|
||||||
{
|
{
|
||||||
var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
|
var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
//convert to Input object.
|
//convert to Input object.
|
||||||
var convertedResult = new TaxRecordInput
|
var convertedResult = new TaxRecordInput
|
||||||
{
|
{
|
||||||
@@ -104,6 +90,7 @@ namespace CarCareTracker.Controllers
|
|||||||
IsRecurring = result.IsRecurring,
|
IsRecurring = result.IsRecurring,
|
||||||
RecurringInterval = result.RecurringInterval,
|
RecurringInterval = result.RecurringInterval,
|
||||||
CustomMonthInterval = result.CustomMonthInterval,
|
CustomMonthInterval = result.CustomMonthInterval,
|
||||||
|
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
|
||||||
Files = result.Files,
|
Files = result.Files,
|
||||||
Tags = result.Tags,
|
Tags = result.Tags,
|
||||||
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
|
||||||
@@ -119,16 +106,16 @@ namespace CarCareTracker.Controllers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
|
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(existingRecord, "taxrecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteTaxRecordById(int taxRecordId)
|
public IActionResult DeleteTaxRecordById(int taxRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteTaxRecordWithChecks(taxRecordId);
|
var result = DeleteTaxRecordWithChecks(taxRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
|
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
|
||||||
{
|
{
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), upgradeRecord.VehicleId))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (upgradeRecord.DeletedRequisitionHistory.Any())
|
if (upgradeRecord.DeletedRequisitionHistory.Any())
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
|
||||||
}
|
}
|
||||||
//push back any reminders
|
//push back any reminders
|
||||||
if (upgradeRecord.ReminderRecordId.Any())
|
if (upgradeRecord.ReminderRecordId.Any())
|
||||||
@@ -61,7 +66,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
|
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), upgradeRecord.VehicleId, User.Identity.Name, $"{(upgradeRecord.Id == default ? "Created" : "Edited")} Upgrade Record - Description: {upgradeRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(upgradeRecord.ToUpgradeRecord(), upgradeRecord.Id == default ? "upgraderecord.add" : "upgraderecord.update", User.Identity.Name));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -74,6 +79,11 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
|
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
|
||||||
{
|
{
|
||||||
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
|
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
|
||||||
|
//security check.
|
||||||
|
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
|
||||||
|
{
|
||||||
|
return Redirect("/Error/Unauthorized");
|
||||||
|
}
|
||||||
//convert to Input object.
|
//convert to Input object.
|
||||||
var convertedResult = new UpgradeRecordInput
|
var convertedResult = new UpgradeRecordInput
|
||||||
{
|
{
|
||||||
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
|
|||||||
//restore any requisitioned supplies.
|
//restore any requisitioned supplies.
|
||||||
if (existingRecord.RequisitionHistory.Any())
|
if (existingRecord.RequisitionHistory.Any())
|
||||||
{
|
{
|
||||||
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
_vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
|
||||||
}
|
}
|
||||||
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id);
|
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "upgraderecord.delete", User.Identity.Name));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
|
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
|
||||||
{
|
{
|
||||||
var result = DeleteUpgradeRecordWithChecks(upgradeRecordId);
|
var result = DeleteUpgradeRecordWithChecks(upgradeRecordId);
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");
|
|
||||||
}
|
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
using CarCareTracker.Models;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using CarCareTracker.Helper;
|
|
||||||
using System.Globalization;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using CarCareTracker.Logic;
|
|
||||||
using CarCareTracker.Filter;
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Logic;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace CarCareTracker.Controllers
|
namespace CarCareTracker.Controllers
|
||||||
@@ -95,7 +95,6 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult Index(int vehicleId)
|
public IActionResult Index(int vehicleId)
|
||||||
{
|
{
|
||||||
var data = _dataAccess.GetVehicleById(vehicleId);
|
var data = _dataAccess.GetVehicleById(vehicleId);
|
||||||
UpdateRecurringTaxes(vehicleId);
|
|
||||||
return View(data);
|
return View(data);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -131,10 +130,11 @@ namespace CarCareTracker.Controllers
|
|||||||
if (isNewAddition)
|
if (isNewAddition)
|
||||||
{
|
{
|
||||||
_userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id);
|
_userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id);
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Added Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Created Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.add", User.Identity.Name, vehicleInput.Id.ToString()));
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Edited Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Updated Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.update", User.Identity.Name, vehicleInput.Id.ToString()));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_dataAccess.DeleteVehicle(vehicleId);
|
_dataAccess.DeleteVehicle(vehicleId);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, "Deleted Vehicle");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic(string.Empty, "vehicle.delete", User.Identity.Name, vehicleId.ToString()));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(OperationResponse.Failed());
|
return Json(OperationResponse.Failed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region "Shared Methods"
|
#region "Shared Methods"
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult GetFilesPendingUpload(List<UploadedFiles> uploadedFiles)
|
public IActionResult GetFilesPendingUpload(List<UploadedFiles> uploadedFiles)
|
||||||
@@ -216,7 +216,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
return Json(searchResults);
|
return Json(searchResults);
|
||||||
}
|
}
|
||||||
foreach(ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs)
|
foreach (ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs)
|
||||||
{
|
{
|
||||||
switch (visibleTab)
|
switch (visibleTab)
|
||||||
{
|
{
|
||||||
@@ -389,7 +389,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Moved multiple {source.ToString()} to {destination.ToString()} - Ids: {string.Join(",", recordIds)}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Moved multiple {source.ToString()} to {destination.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.move", User.Identity.Name, string.Empty));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -431,7 +431,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Deleted multiple {importMode.ToString()} - Ids: {string.Join(", ", recordIds)}", "bulk.delete", User.Identity.Name, string.Empty));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -490,7 +490,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Adjusted odometer for multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Adjusted odometer for multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.odometer.adjust", User.Identity.Name, string.Empty));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -582,7 +582,7 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.duplicate", User.Identity.Name, string.Empty));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -717,7 +717,71 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)} - to Vehicle Ids: {string.Join(",", vehicleIds)}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)} - to Vehicle Ids: {string.Join(",", vehicleIds)}", "bulk.duplicate.to.vehicles", User.Identity.Name, string.Join(",", vehicleIds)));
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult BulkCreateOdometerRecords(List<int> recordIds, ImportMode importMode)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
switch (importMode)
|
||||||
|
{
|
||||||
|
case ImportMode.ServiceRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
|
||||||
|
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = existingRecord.Date,
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Mileage = existingRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Service Record: {existingRecord.Description}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.RepairRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
|
||||||
|
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = existingRecord.Date,
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Mileage = existingRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Repair Record: {existingRecord.Description}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.UpgradeRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
|
||||||
|
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = existingRecord.Date,
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Mileage = existingRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Upgrade Record: {existingRecord.Description}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.GasRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||||
|
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = existingRecord.Date,
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Mileage = existingRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Gas Record. {existingRecord.Notes}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Created Odometer Records based on {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.odometer.insert", User.Identity.Name, string.Empty));
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -780,14 +844,15 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
if (extraFieldIsEdited)
|
if (extraFieldIsEdited)
|
||||||
{
|
{
|
||||||
foreach(ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields)
|
foreach (ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields)
|
||||||
{
|
{
|
||||||
if (existingRecord.ExtraFields.Any(x=>x.Name == extraField.Name))
|
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
|
||||||
{
|
{
|
||||||
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
|
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
|
||||||
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
|
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
|
||||||
existingRecord.ExtraFields.Insert(insertIndex, extraField);
|
existingRecord.ExtraFields.Insert(insertIndex, extraField);
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
existingRecord.ExtraFields.Add(extraField);
|
existingRecord.ExtraFields.Add(extraField);
|
||||||
}
|
}
|
||||||
@@ -893,6 +958,167 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
public IActionResult PrintRecordStickers(int vehicleId, List<int> recordIds, ImportMode importMode)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (!recordIds.Any())
|
||||||
|
{
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
var stickerViewModel = new StickerViewModel() { RecordType = importMode };
|
||||||
|
if (vehicleId != default)
|
||||||
|
{
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
if (vehicleData != null && vehicleData.Id != default)
|
||||||
|
{
|
||||||
|
stickerViewModel.VehicleData = vehicleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int recordsAdded = 0;
|
||||||
|
switch (importMode)
|
||||||
|
{
|
||||||
|
case ImportMode.ServiceRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
stickerViewModel.GenericRecords.Add(_serviceRecordDataAccess.GetServiceRecordById(recordId));
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.RepairRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
stickerViewModel.GenericRecords.Add(_collisionRecordDataAccess.GetCollisionRecordById(recordId));
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.UpgradeRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
stickerViewModel.GenericRecords.Add(_upgradeRecordDataAccess.GetUpgradeRecordById(recordId));
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.GasRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
var record = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||||
|
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||||
|
{
|
||||||
|
Cost = record.Cost,
|
||||||
|
Date = record.Date,
|
||||||
|
Notes = record.Notes,
|
||||||
|
Mileage = record.Mileage,
|
||||||
|
ExtraFields = record.ExtraFields
|
||||||
|
});
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.TaxRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
var record = _taxRecordDataAccess.GetTaxRecordById(recordId);
|
||||||
|
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||||
|
{
|
||||||
|
Description = record.Description,
|
||||||
|
Cost = record.Cost,
|
||||||
|
Notes = record.Notes,
|
||||||
|
Date = record.Date,
|
||||||
|
ExtraFields = record.ExtraFields
|
||||||
|
});
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.SupplyRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
var record = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
|
||||||
|
stickerViewModel.SupplyRecords.Add(record);
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.NoteRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
var record = _noteDataAccess.GetNoteById(recordId);
|
||||||
|
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||||
|
{
|
||||||
|
Description = record.Description,
|
||||||
|
Notes = record.NoteText
|
||||||
|
});
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.OdometerRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
var record = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
|
||||||
|
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||||
|
{
|
||||||
|
Date = record.Date,
|
||||||
|
Mileage = record.Mileage,
|
||||||
|
Notes = record.Notes,
|
||||||
|
ExtraFields = record.ExtraFields
|
||||||
|
});
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.ReminderRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
stickerViewModel.ReminderRecords.Add(_reminderRecordDataAccess.GetReminderRecordById(recordId));
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.PlanRecord:
|
||||||
|
{
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
var record = _planRecordDataAccess.GetPlanRecordById(recordId);
|
||||||
|
stickerViewModel.GenericRecords.Add(new GenericRecord
|
||||||
|
{
|
||||||
|
Description = record.Description,
|
||||||
|
Cost = record.Cost,
|
||||||
|
Notes = record.Notes,
|
||||||
|
Date = record.DateModified,
|
||||||
|
ExtraFields = record.ExtraFields,
|
||||||
|
RequisitionHistory = record.RequisitionHistory
|
||||||
|
});
|
||||||
|
recordsAdded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (recordsAdded > 0)
|
||||||
|
{
|
||||||
|
return PartialView("_Stickers", stickerViewModel);
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
public IActionResult SaveUserColumnPreferences(UserColumnPreference columnPreference)
|
public IActionResult SaveUserColumnPreferences(UserColumnPreference columnPreference)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -903,6 +1129,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
var existingPreference = existingUserColumnPreference.Single();
|
var existingPreference = existingUserColumnPreference.Single();
|
||||||
existingPreference.VisibleColumns = columnPreference.VisibleColumns;
|
existingPreference.VisibleColumns = columnPreference.VisibleColumns;
|
||||||
|
existingPreference.ColumnOrder = columnPreference.ColumnOrder;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Enum/ReminderIntervalUnit.cs
Normal file
8
Enum/ReminderIntervalUnit.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public enum ReminderIntervalUnit
|
||||||
|
{
|
||||||
|
Months = 1,
|
||||||
|
Days = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Enum/TagFilter.cs
Normal file
8
Enum/TagFilter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public enum TagFilter
|
||||||
|
{
|
||||||
|
Exclude = 0,
|
||||||
|
IncludeOnly = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,27 +19,34 @@ namespace CarCareTracker.Filter
|
|||||||
{
|
{
|
||||||
if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser)))
|
if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
{
|
{
|
||||||
var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString());
|
if (filterContext.ActionArguments.ContainsKey("vehicleId"))
|
||||||
if (vehicleId != default)
|
|
||||||
{
|
{
|
||||||
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString());
|
||||||
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
|
if (vehicleId != default)
|
||||||
{
|
{
|
||||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
|
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
|
||||||
|
{
|
||||||
|
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" };
|
||||||
|
if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
|
||||||
|
{
|
||||||
|
//user trying to access shop supplies but shop supplies is not enabled by root user.
|
||||||
|
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||||
|
}
|
||||||
|
else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
|
||||||
|
{
|
||||||
|
//user trying to access any other endpoints using 0 as vehicle id.
|
||||||
|
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" };
|
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
||||||
if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
|
|
||||||
{
|
|
||||||
//user trying to access shop supplies but shop supplies is not enabled by root user.
|
|
||||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
|
||||||
}
|
|
||||||
else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
|
|
||||||
{
|
|
||||||
//user trying to access any other endpoints using 0 as vehicle id.
|
|
||||||
filterContext.Result = new RedirectResult("/Error/Unauthorized");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,16 @@ namespace CarCareTracker.Helper
|
|||||||
bool GetCustomWidgetsEnabled();
|
bool GetCustomWidgetsEnabled();
|
||||||
string GetMOTD();
|
string GetMOTD();
|
||||||
string GetLogoUrl();
|
string GetLogoUrl();
|
||||||
|
string GetSmallLogoUrl();
|
||||||
string GetServerLanguage();
|
string GetServerLanguage();
|
||||||
bool GetServerDisabledRegistration();
|
bool GetServerDisabledRegistration();
|
||||||
bool GetServerEnableShopSupplies();
|
bool GetServerEnableShopSupplies();
|
||||||
string GetServerPostgresConnection();
|
string GetServerPostgresConnection();
|
||||||
string GetAllowedFileUploadExtensions();
|
string GetAllowedFileUploadExtensions();
|
||||||
public bool DeleteUserConfig(int userId);
|
string GetServerDomain();
|
||||||
|
bool DeleteUserConfig(int userId);
|
||||||
|
bool GetInvariantApi();
|
||||||
|
bool GetServerOpenRegistration();
|
||||||
}
|
}
|
||||||
public class ConfigHelper : IConfigHelper
|
public class ConfigHelper : IConfigHelper
|
||||||
{
|
{
|
||||||
@@ -51,11 +55,24 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS"));
|
return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS"));
|
||||||
}
|
}
|
||||||
|
public bool GetInvariantApi()
|
||||||
|
{
|
||||||
|
return CheckBool(CheckString("LUBELOGGER_INVARIANT_API"));
|
||||||
|
}
|
||||||
public string GetMOTD()
|
public string GetMOTD()
|
||||||
{
|
{
|
||||||
var motd = CheckString("LUBELOGGER_MOTD");
|
var motd = CheckString("LUBELOGGER_MOTD");
|
||||||
return motd;
|
return motd;
|
||||||
}
|
}
|
||||||
|
public string GetServerDomain()
|
||||||
|
{
|
||||||
|
var domain = CheckString("LUBELOGGER_DOMAIN");
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
public bool GetServerOpenRegistration()
|
||||||
|
{
|
||||||
|
return CheckBool(CheckString("LUBELOGGER_OPEN_REGISTRATION"));
|
||||||
|
}
|
||||||
public OpenIDConfig GetOpenIDConfig()
|
public OpenIDConfig GetOpenIDConfig()
|
||||||
{
|
{
|
||||||
OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig();
|
OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig();
|
||||||
@@ -76,6 +93,11 @@ namespace CarCareTracker.Helper
|
|||||||
var logoUrl = CheckString("LUBELOGGER_LOGO_URL", "/defaults/lubelogger_logo.png");
|
var logoUrl = CheckString("LUBELOGGER_LOGO_URL", "/defaults/lubelogger_logo.png");
|
||||||
return logoUrl;
|
return logoUrl;
|
||||||
}
|
}
|
||||||
|
public string GetSmallLogoUrl()
|
||||||
|
{
|
||||||
|
var logoUrl = CheckString("LUBELOGGER_LOGO_SMALL_URL", "/defaults/lubelogger_logo_small.png");
|
||||||
|
return logoUrl;
|
||||||
|
}
|
||||||
public string GetAllowedFileUploadExtensions()
|
public string GetAllowedFileUploadExtensions()
|
||||||
{
|
{
|
||||||
var allowedFileExtensions = CheckString("LUBELOGGER_ALLOWED_FILE_EXTENSIONS", StaticHelper.DefaultAllowedFileExtensions);
|
var allowedFileExtensions = CheckString("LUBELOGGER_ALLOWED_FILE_EXTENSIONS", StaticHelper.DefaultAllowedFileExtensions);
|
||||||
@@ -224,9 +246,11 @@ namespace CarCareTracker.Helper
|
|||||||
EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))),
|
EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))),
|
||||||
PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)),
|
PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)),
|
||||||
PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)),
|
PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)),
|
||||||
|
UseUnitForFuelCost = CheckBool(CheckString(nameof(UserConfig.UseUnitForFuelCost))),
|
||||||
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,
|
||||||
@@ -234,7 +258,8 @@ namespace CarCareTracker.Helper
|
|||||||
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
|
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
|
||||||
DefaultTab = (ImportMode)int.Parse(CheckString(nameof(UserConfig.DefaultTab), "8")),
|
DefaultTab = (ImportMode)int.Parse(CheckString(nameof(UserConfig.DefaultTab), "8")),
|
||||||
DefaultReminderEmail = CheckString(nameof(UserConfig.DefaultReminderEmail)),
|
DefaultReminderEmail = CheckString(nameof(UserConfig.DefaultReminderEmail)),
|
||||||
DisableRegistration = CheckBool(CheckString(nameof(UserConfig.DisableRegistration)))
|
DisableRegistration = CheckBool(CheckString(nameof(UserConfig.DisableRegistration))),
|
||||||
|
ShowVehicleThumbnail = CheckBool(CheckString(nameof(UserConfig.ShowVehicleThumbnail)))
|
||||||
};
|
};
|
||||||
int userId = 0;
|
int userId = 0;
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace CarCareTracker.Helper
|
|||||||
public interface IFileHelper
|
public interface IFileHelper
|
||||||
{
|
{
|
||||||
string GetFullFilePath(string currentFilePath, bool mustExist = true);
|
string GetFullFilePath(string currentFilePath, bool mustExist = true);
|
||||||
|
byte[] GetFileBytes(string fullFilePath, bool deleteFile = false);
|
||||||
string MoveFileFromTemp(string currentFilePath, string newFolder);
|
string MoveFileFromTemp(string currentFilePath, string newFolder);
|
||||||
bool RenameFile(string currentFilePath, string newName);
|
bool RenameFile(string currentFilePath, string newName);
|
||||||
bool DeleteFile(string currentFilePath);
|
bool DeleteFile(string currentFilePath);
|
||||||
@@ -34,7 +35,7 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
public List<string> GetLanguages()
|
public List<string> GetLanguages()
|
||||||
{
|
{
|
||||||
var languagePath = Path.Combine(_webEnv.WebRootPath, "translations");
|
var languagePath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
|
||||||
var defaultList = new List<string>() { "en_US" };
|
var defaultList = new List<string>() { "en_US" };
|
||||||
if (Directory.Exists(languagePath))
|
if (Directory.Exists(languagePath))
|
||||||
{
|
{
|
||||||
@@ -72,7 +73,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
currentFilePath = currentFilePath.Substring(1);
|
currentFilePath = currentFilePath.Substring(1);
|
||||||
}
|
}
|
||||||
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
|
string oldFilePath = currentFilePath.StartsWith("defaults/") ? Path.Combine(_webEnv.WebRootPath, currentFilePath) : Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
|
||||||
if (File.Exists(oldFilePath))
|
if (File.Exists(oldFilePath))
|
||||||
{
|
{
|
||||||
return oldFilePath;
|
return oldFilePath;
|
||||||
@@ -85,6 +86,19 @@ namespace CarCareTracker.Helper
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public byte[] GetFileBytes(string fullFilePath, bool deleteFile = false)
|
||||||
|
{
|
||||||
|
if (File.Exists(fullFilePath))
|
||||||
|
{
|
||||||
|
var fileBytes = File.ReadAllBytes(fullFilePath);
|
||||||
|
if (deleteFile)
|
||||||
|
{
|
||||||
|
File.Delete(fullFilePath);
|
||||||
|
}
|
||||||
|
return fileBytes;
|
||||||
|
}
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
}
|
||||||
public bool RestoreBackup(string fileName, bool clearExisting = false)
|
public bool RestoreBackup(string fileName, bool clearExisting = false)
|
||||||
{
|
{
|
||||||
var fullFilePath = GetFullFilePath(fileName);
|
var fullFilePath = GetFullFilePath(fileName);
|
||||||
@@ -94,7 +108,7 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{Guid.NewGuid()}");
|
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{Guid.NewGuid()}");
|
||||||
if (!Directory.Exists(tempPath))
|
if (!Directory.Exists(tempPath))
|
||||||
Directory.CreateDirectory(tempPath);
|
Directory.CreateDirectory(tempPath);
|
||||||
//extract zip file
|
//extract zip file
|
||||||
@@ -105,10 +119,10 @@ namespace CarCareTracker.Helper
|
|||||||
var translationPath = Path.Combine(tempPath, "translations");
|
var translationPath = Path.Combine(tempPath, "translations");
|
||||||
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
||||||
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
|
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
|
||||||
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
var configPath = Path.Combine(tempPath, StaticHelper.LegacyUserConfigPath);
|
||||||
if (Directory.Exists(imagePath))
|
if (Directory.Exists(imagePath))
|
||||||
{
|
{
|
||||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "images");
|
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
|
||||||
if (!Directory.Exists(existingPath))
|
if (!Directory.Exists(existingPath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(existingPath);
|
Directory.CreateDirectory(existingPath);
|
||||||
@@ -130,7 +144,7 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
if (Directory.Exists(documentPath))
|
if (Directory.Exists(documentPath))
|
||||||
{
|
{
|
||||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
|
||||||
if (!Directory.Exists(existingPath))
|
if (!Directory.Exists(existingPath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(existingPath);
|
Directory.CreateDirectory(existingPath);
|
||||||
@@ -152,7 +166,7 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
if (Directory.Exists(translationPath))
|
if (Directory.Exists(translationPath))
|
||||||
{
|
{
|
||||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
|
||||||
if (!Directory.Exists(existingPath))
|
if (!Directory.Exists(existingPath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(existingPath);
|
Directory.CreateDirectory(existingPath);
|
||||||
@@ -186,9 +200,9 @@ namespace CarCareTracker.Helper
|
|||||||
if (File.Exists(configPath))
|
if (File.Exists(configPath))
|
||||||
{
|
{
|
||||||
//check if config folder exists.
|
//check if config folder exists.
|
||||||
if (!Directory.Exists("config/"))
|
if (!Directory.Exists("data/config"))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory("config/");
|
Directory.CreateDirectory("data/config");
|
||||||
}
|
}
|
||||||
File.Move(configPath, StaticHelper.UserConfigPath, true);
|
File.Move(configPath, StaticHelper.UserConfigPath, true);
|
||||||
}
|
}
|
||||||
@@ -203,7 +217,7 @@ namespace CarCareTracker.Helper
|
|||||||
public string MakeAttachmentsExport(List<GenericReportModel> exportData)
|
public string MakeAttachmentsExport(List<GenericReportModel> exportData)
|
||||||
{
|
{
|
||||||
var folderName = Guid.NewGuid();
|
var folderName = Guid.NewGuid();
|
||||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
|
||||||
if (!Directory.Exists(tempPath))
|
if (!Directory.Exists(tempPath))
|
||||||
Directory.CreateDirectory(tempPath);
|
Directory.CreateDirectory(tempPath);
|
||||||
int fileIndex = 0;
|
int fileIndex = 0;
|
||||||
@@ -227,10 +241,10 @@ namespace CarCareTracker.Helper
|
|||||||
public string MakeBackup()
|
public string MakeBackup()
|
||||||
{
|
{
|
||||||
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
|
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
|
||||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
|
||||||
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
|
var imagePath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
|
||||||
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
var documentPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
|
||||||
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
var translationPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
|
||||||
var dataPath = StaticHelper.DbName;
|
var dataPath = StaticHelper.DbName;
|
||||||
var widgetPath = StaticHelper.AdditionalWidgetsPath;
|
var widgetPath = StaticHelper.AdditionalWidgetsPath;
|
||||||
var configPath = StaticHelper.UserConfigPath;
|
var configPath = StaticHelper.UserConfigPath;
|
||||||
@@ -301,8 +315,8 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
currentFilePath = currentFilePath.Substring(1);
|
currentFilePath = currentFilePath.Substring(1);
|
||||||
}
|
}
|
||||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, newFolder);
|
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", newFolder);
|
||||||
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
|
string oldFilePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
|
||||||
if (!Directory.Exists(uploadPath))
|
if (!Directory.Exists(uploadPath))
|
||||||
Directory.CreateDirectory(uploadPath);
|
Directory.CreateDirectory(uploadPath);
|
||||||
string newFileUploadPath = oldFilePath.Replace(tempPath, newFolder);
|
string newFileUploadPath = oldFilePath.Replace(tempPath, newFolder);
|
||||||
@@ -319,7 +333,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
currentFilePath = currentFilePath.Substring(1);
|
currentFilePath = currentFilePath.Substring(1);
|
||||||
}
|
}
|
||||||
string filePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
|
string filePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
|
||||||
if (File.Exists(filePath))
|
if (File.Exists(filePath))
|
||||||
{
|
{
|
||||||
File.Delete(filePath);
|
File.Delete(filePath);
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ namespace CarCareTracker.Helper
|
|||||||
MissedFuelUp = currentObject.MissedFuelUp,
|
MissedFuelUp = currentObject.MissedFuelUp,
|
||||||
Notes = currentObject.Notes,
|
Notes = currentObject.Notes,
|
||||||
Tags = currentObject.Tags,
|
Tags = currentObject.Tags,
|
||||||
ExtraFields = currentObject.ExtraFields
|
ExtraFields = currentObject.ExtraFields,
|
||||||
|
Files = currentObject.Files
|
||||||
};
|
};
|
||||||
if (currentObject.MissedFuelUp)
|
if (currentObject.MissedFuelUp)
|
||||||
{
|
{
|
||||||
@@ -86,9 +87,9 @@ namespace CarCareTracker.Helper
|
|||||||
unFactoredConsumption = 0;
|
unFactoredConsumption = 0;
|
||||||
unFactoredMileage = 0;
|
unFactoredMileage = 0;
|
||||||
}
|
}
|
||||||
else if (currentObject.IsFillToFull)
|
else if (currentObject.IsFillToFull && currentObject.Mileage != default)
|
||||||
{
|
{
|
||||||
//if user filled to full.
|
//if user filled to full and an odometer is provided, otherwise we will defer calculations
|
||||||
if (convertedConsumption > 0.00M && deltaMileage > 0)
|
if (convertedConsumption > 0.00M && deltaMileage > 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -130,10 +131,14 @@ namespace CarCareTracker.Helper
|
|||||||
MissedFuelUp = currentObject.MissedFuelUp,
|
MissedFuelUp = currentObject.MissedFuelUp,
|
||||||
Notes = currentObject.Notes,
|
Notes = currentObject.Notes,
|
||||||
Tags = currentObject.Tags,
|
Tags = currentObject.Tags,
|
||||||
ExtraFields = currentObject.ExtraFields
|
ExtraFields = currentObject.ExtraFields,
|
||||||
|
Files = currentObject.Files
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
previousMileage = currentObject.Mileage;
|
if (currentObject.Mileage != default)
|
||||||
|
{
|
||||||
|
previousMileage = currentObject.Mileage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return computedResults;
|
return computedResults;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,28 @@ 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
|
||||||
{
|
{
|
||||||
private readonly MailConfig mailConfig;
|
private readonly MailConfig mailConfig;
|
||||||
|
private readonly string serverLanguage;
|
||||||
|
private readonly string serverDomain;
|
||||||
private readonly IFileHelper _fileHelper;
|
private readonly IFileHelper _fileHelper;
|
||||||
|
private readonly ITranslationHelper _translator;
|
||||||
private readonly ILogger<MailHelper> _logger;
|
private readonly ILogger<MailHelper> _logger;
|
||||||
public MailHelper(
|
public MailHelper(
|
||||||
IConfigHelper config,
|
IConfigHelper config,
|
||||||
IFileHelper fileHelper,
|
IFileHelper fileHelper,
|
||||||
|
ITranslationHelper translationHelper,
|
||||||
ILogger<MailHelper> logger
|
ILogger<MailHelper> logger
|
||||||
) {
|
) {
|
||||||
//load mailConfig from Configuration
|
//load mailConfig from Configuration
|
||||||
mailConfig = config.GetMailConfig();
|
mailConfig = config.GetMailConfig();
|
||||||
|
serverLanguage = config.GetServerLanguage();
|
||||||
|
serverDomain = config.GetServerDomain();
|
||||||
_fileHelper = fileHelper;
|
_fileHelper = fileHelper;
|
||||||
|
_translator = translationHelper;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
|
public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
|
||||||
@@ -36,8 +44,15 @@ namespace CarCareTracker.Helper
|
|||||||
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
|
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
|
||||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||||
}
|
}
|
||||||
string emailSubject = "Your Registration Token for LubeLogger";
|
string emailSubject = _translator.Translate(serverLanguage, "Your Registration Token for LubeLogger");
|
||||||
string emailBody = $"A token has been generated on your behalf, please complete your registration for LubeLogger using the token: {token}";
|
string tokenHtml = token;
|
||||||
|
if (!string.IsNullOrWhiteSpace(serverDomain))
|
||||||
|
{
|
||||||
|
string cleanedURL = serverDomain.EndsWith('/') ? serverDomain.TrimEnd('/') : serverDomain;
|
||||||
|
//construct registration URL.
|
||||||
|
tokenHtml = $"<a href='{cleanedURL}/Login/Registration?email={emailAddress}&token={token}' target='_blank'>{token}</a>";
|
||||||
|
}
|
||||||
|
string emailBody = $"<span>{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please complete your registration for LubeLogger using the token")}: {tokenHtml}</span>";
|
||||||
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
@@ -57,8 +72,37 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||||
}
|
}
|
||||||
string emailSubject = "Your Password Reset Token for LubeLogger";
|
string emailSubject = _translator.Translate(serverLanguage, "Your Password Reset Token for LubeLogger");
|
||||||
string emailBody = $"A token has been generated on your behalf, please reset your password for LubeLogger using the token: {token}";
|
string tokenHtml = token;
|
||||||
|
if (!string.IsNullOrWhiteSpace(serverDomain))
|
||||||
|
{
|
||||||
|
string cleanedURL = serverDomain.EndsWith('/') ? serverDomain.TrimEnd('/') : serverDomain;
|
||||||
|
//construct registration URL.
|
||||||
|
tokenHtml = $"<a href='{cleanedURL}/Login/ResetPassword?email={emailAddress}&token={token}' target='_blank'>{token}</a>";
|
||||||
|
}
|
||||||
|
string emailBody = $"<span>{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please reset your password for LubeLogger using the token")}: {tokenHtml}</span>";
|
||||||
|
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return OperationResponse.Succeed("Email Sent!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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);
|
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
@@ -79,8 +123,8 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
return OperationResponse.Failed("Email Address or Token is invalid");
|
return OperationResponse.Failed("Email Address or Token is invalid");
|
||||||
}
|
}
|
||||||
string emailSubject = "Your User Account Update Token for LubeLogger";
|
string emailSubject = _translator.Translate(serverLanguage, "Your User Account Update Token for LubeLogger");
|
||||||
string emailBody = $"A token has been generated on your behalf, please update your account for LubeLogger using the token: {token}";
|
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please update your account for LubeLogger using the token")}: {token}";
|
||||||
var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody);
|
var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
@@ -107,17 +151,18 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
//get email template, this file has to exist since it's a static file.
|
//get email template, this file has to exist since it's a static file.
|
||||||
var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate);
|
var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate);
|
||||||
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
|
string emailSubject = $"{_translator.Translate(serverLanguage, "Vehicle Reminders From LubeLogger")} - {DateTime.Now.ToShortDateString()}";
|
||||||
//construct html table.
|
//construct html table.
|
||||||
string emailBody = File.ReadAllText(emailTemplatePath);
|
string emailBody = File.ReadAllText(emailTemplatePath);
|
||||||
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}");
|
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}");
|
||||||
|
string tableHeader = $"<th>{_translator.Translate(serverLanguage, "Urgency")}</th><th>{_translator.Translate(serverLanguage, "Description")}</th><th>{_translator.Translate(serverLanguage, "Due")}</th>";
|
||||||
string tableBody = "";
|
string tableBody = "";
|
||||||
foreach(ReminderRecordViewModel reminder in reminders)
|
foreach(ReminderRecordViewModel reminder in reminders)
|
||||||
{
|
{
|
||||||
var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date.ToShortDateString()} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}";
|
var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date.ToShortDateString()} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}";
|
||||||
tableBody += $"<tr class='{reminder.Urgency}'><td>{StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency)}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
|
tableBody += $"<tr class='{reminder.Urgency}'><td>{_translator.Translate(serverLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
|
||||||
}
|
}
|
||||||
emailBody = emailBody.Replace("{TableBody}", tableBody);
|
emailBody = emailBody.Replace("{TableHeader}", tableHeader).Replace("{TableBody}", tableBody);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = SendEmail(emailAddresses, emailSubject, emailBody);
|
var result = SendEmail(emailAddresses, emailSubject, emailBody);
|
||||||
|
|||||||
@@ -25,7 +25,14 @@ namespace CarCareTracker.Helper
|
|||||||
existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval);
|
existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
|
if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
|
||||||
|
{
|
||||||
|
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
|
||||||
|
}
|
||||||
|
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
|
||||||
|
{
|
||||||
|
existingReminder.Date = newDate.Date.AddDays(existingReminder.CustomMonthInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
|
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
|
||||||
@@ -55,7 +62,14 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
|
if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
|
||||||
|
{
|
||||||
|
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
|
||||||
|
}
|
||||||
|
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
|
||||||
|
{
|
||||||
|
existingReminder.Date = newDate.AddDays(existingReminder.CustomMonthInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return existingReminder;
|
return existingReminder;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using CsvHelper;
|
using CsvHelper;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace CarCareTracker.Helper
|
namespace CarCareTracker.Helper
|
||||||
{
|
{
|
||||||
@@ -9,15 +12,17 @@ namespace CarCareTracker.Helper
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StaticHelper
|
public static class StaticHelper
|
||||||
{
|
{
|
||||||
public const string VersionNumber = "1.4.1";
|
public const string VersionNumber = "1.4.8";
|
||||||
public const string DbName = "data/cartracker.db";
|
public const string DbName = "data/cartracker.db";
|
||||||
public const string UserConfigPath = "config/userConfig.json";
|
public const string UserConfigPath = "data/config/userConfig.json";
|
||||||
|
public const string LegacyUserConfigPath = "config/userConfig.json";
|
||||||
public const string AdditionalWidgetsPath = "data/widgets.html";
|
public const string AdditionalWidgetsPath = "data/widgets.html";
|
||||||
public const string GenericErrorMessage = "An error occurred, please try again later";
|
public const string GenericErrorMessage = "An error occurred, please try again later";
|
||||||
public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
||||||
public const string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
public const string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
||||||
public const string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
|
public const string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
|
||||||
public const string TranslationPath = "https://hargata.github.io/lubelog_translations";
|
public const string TranslationPath = "https://hargata.github.io/lubelog_translations";
|
||||||
|
public const string ReleasePath = "https://api.github.com/repos/hargata/lubelog/releases/latest";
|
||||||
public const string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
|
public const string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
|
||||||
public const string ReportNote = "Report generated by LubeLogger, a Free and Open Source Vehicle Maintenance Tracker - LubeLogger.com";
|
public const string ReportNote = "Report generated by LubeLogger, a Free and Open Source Vehicle Maintenance Tracker - LubeLogger.com";
|
||||||
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
||||||
@@ -239,7 +244,8 @@ namespace CarCareTracker.Helper
|
|||||||
|
|
||||||
public static List<ExtraField> AddExtraFields(List<ExtraField> recordExtraFields, List<ExtraField> templateExtraFields)
|
public static List<ExtraField> AddExtraFields(List<ExtraField> recordExtraFields, List<ExtraField> templateExtraFields)
|
||||||
{
|
{
|
||||||
if (!templateExtraFields.Any()) {
|
if (!templateExtraFields.Any())
|
||||||
|
{
|
||||||
return new List<ExtraField>();
|
return new List<ExtraField>();
|
||||||
}
|
}
|
||||||
if (!recordExtraFields.Any())
|
if (!recordExtraFields.Any())
|
||||||
@@ -257,10 +263,12 @@ 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)
|
||||||
{
|
{
|
||||||
if (!recordFieldNames.Contains(extraField.Name))
|
if (!recordFieldNames.Contains(extraField.Name))
|
||||||
{
|
{
|
||||||
@@ -308,7 +316,8 @@ namespace CarCareTracker.Helper
|
|||||||
if (mailConfig != null && !string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
if (mailConfig != null && !string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"SMTP Configured for {mailConfig.EmailServer}");
|
Console.WriteLine($"SMTP Configured for {mailConfig.EmailServer}");
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("SMTP Not Configured");
|
Console.WriteLine("SMTP Not Configured");
|
||||||
}
|
}
|
||||||
@@ -316,23 +325,113 @@ namespace CarCareTracker.Helper
|
|||||||
Console.WriteLine($"Message Of The Day: {motd}");
|
Console.WriteLine($"Message Of The Day: {motd}");
|
||||||
if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name))
|
if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name))
|
||||||
{
|
{
|
||||||
Console.WriteLine("No Locale or Culture Configured for LubeLogger, Check Environment Variables");
|
Console.WriteLine("WARNING: No Locale or Culture Configured for LubeLogger, Check Environment Variables");
|
||||||
|
}
|
||||||
|
//Create folders if they don't exist.
|
||||||
|
if (!Directory.Exists("data"))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("data");
|
||||||
|
Console.WriteLine("Created data directory");
|
||||||
|
}
|
||||||
|
if (!Directory.Exists("data/images"))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Created images directory");
|
||||||
|
Directory.CreateDirectory("data/images");
|
||||||
|
}
|
||||||
|
if (!Directory.Exists("data/documents"))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("data/documents");
|
||||||
|
Console.WriteLine("Created documents directory");
|
||||||
|
}
|
||||||
|
if (!Directory.Exists("data/translations"))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("data/translations");
|
||||||
|
Console.WriteLine("Created translations directory");
|
||||||
|
}
|
||||||
|
if (!Directory.Exists("data/temp"))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("data/temp");
|
||||||
|
Console.WriteLine("Created translations directory");
|
||||||
|
}
|
||||||
|
if (!Directory.Exists("data/config"))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("data/config");
|
||||||
|
Console.WriteLine("Created config directory");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
|
public static void CheckMigration(string webRootPath, string webContentPath)
|
||||||
|
{
|
||||||
|
//check if current working directory differs from content root.
|
||||||
|
if (Directory.GetCurrentDirectory() != webContentPath)
|
||||||
|
{
|
||||||
|
Console.WriteLine("WARNING: The Working Directory differs from the Web Content Path");
|
||||||
|
Console.WriteLine($"Working Directory: {Directory.GetCurrentDirectory()}");
|
||||||
|
Console.WriteLine($"Web Content Path: {webContentPath}");
|
||||||
|
}
|
||||||
|
//migrates all user-uploaded files from webroot to new data folder
|
||||||
|
//images
|
||||||
|
var imagePath = Path.Combine(webRootPath, "images");
|
||||||
|
var docsPath = Path.Combine(webRootPath, "documents");
|
||||||
|
var translationPath = Path.Combine(webRootPath, "translations");
|
||||||
|
var tempPath = Path.Combine(webRootPath, "temp");
|
||||||
|
if (File.Exists(LegacyUserConfigPath))
|
||||||
|
{
|
||||||
|
File.Move(LegacyUserConfigPath, UserConfigPath, true);
|
||||||
|
}
|
||||||
|
if (Directory.Exists(imagePath))
|
||||||
|
{
|
||||||
|
foreach (string fileToMove in Directory.GetFiles(imagePath))
|
||||||
|
{
|
||||||
|
var newFilePath = $"data/images/{Path.GetFileName(fileToMove)}";
|
||||||
|
File.Move(fileToMove, newFilePath, true);
|
||||||
|
Console.WriteLine($"Migrated Image: {Path.GetFileName(fileToMove)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Directory.Exists(docsPath))
|
||||||
|
{
|
||||||
|
foreach (string fileToMove in Directory.GetFiles(docsPath))
|
||||||
|
{
|
||||||
|
var newFilePath = $"data/documents/{Path.GetFileName(fileToMove)}";
|
||||||
|
File.Move(fileToMove, newFilePath, true);
|
||||||
|
Console.WriteLine($"Migrated Document: {Path.GetFileName(fileToMove)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Directory.Exists(translationPath))
|
||||||
|
{
|
||||||
|
foreach (string fileToMove in Directory.GetFiles(translationPath))
|
||||||
|
{
|
||||||
|
var newFilePath = $"data/translations/{Path.GetFileName(fileToMove)}";
|
||||||
|
File.Move(fileToMove, newFilePath, true);
|
||||||
|
Console.WriteLine($"Migrated Translation: {Path.GetFileName(fileToMove)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Directory.Exists(tempPath))
|
||||||
|
{
|
||||||
|
foreach (string fileToMove in Directory.GetFiles(tempPath))
|
||||||
|
{
|
||||||
|
var newFilePath = $"data/temp/{Path.GetFileName(fileToMove)}";
|
||||||
|
File.Move(fileToMove, newFilePath, true);
|
||||||
|
Console.WriteLine($"Migrated Temp File: {Path.GetFileName(fileToMove)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async void NotifyAsync(string webhookURL, WebHookPayload webHookPayload)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(webhookURL))
|
if (string.IsNullOrWhiteSpace(webhookURL))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var httpClient = new HttpClient();
|
var httpClient = new HttpClient();
|
||||||
var httpParams = new Dictionary<string, string>
|
if (webhookURL.StartsWith("discord://"))
|
||||||
{
|
{
|
||||||
{ "vehicleId", vehicleId.ToString() },
|
webhookURL = webhookURL.Replace("discord://", "https://"); //cleanurl
|
||||||
{ "username", username },
|
//format to discord
|
||||||
{ "action", action },
|
httpClient.PostAsJsonAsync(webhookURL, DiscordWebHook.FromWebHookPayload(webHookPayload));
|
||||||
};
|
}
|
||||||
httpClient.PostAsJsonAsync(webhookURL, httpParams);
|
else
|
||||||
|
{
|
||||||
|
httpClient.PostAsJsonAsync(webhookURL, webHookPayload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public static string GetImportModeIcon(ImportMode importMode)
|
public static string GetImportModeIcon(ImportMode importMode)
|
||||||
{
|
{
|
||||||
@@ -367,12 +466,14 @@ namespace CarCareTracker.Helper
|
|||||||
if (vehicle.VehicleIdentifier == "LicensePlate")
|
if (vehicle.VehicleIdentifier == "LicensePlate")
|
||||||
{
|
{
|
||||||
return vehicle.LicensePlate;
|
return vehicle.LicensePlate;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (vehicle.ExtraFields.Any(x=>x.Name == vehicle.VehicleIdentifier))
|
if (vehicle.ExtraFields.Any(x => x.Name == vehicle.VehicleIdentifier))
|
||||||
{
|
{
|
||||||
return vehicle.ExtraFields?.FirstOrDefault(x=>x.Name == vehicle.VehicleIdentifier)?.Value;
|
return vehicle.ExtraFields?.FirstOrDefault(x => x.Name == vehicle.VehicleIdentifier)?.Value;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return "N/A";
|
return "N/A";
|
||||||
}
|
}
|
||||||
@@ -399,10 +500,11 @@ namespace CarCareTracker.Helper
|
|||||||
//Translations
|
//Translations
|
||||||
public static string GetTranslationDownloadPath(string continent, string name)
|
public static string GetTranslationDownloadPath(string continent, string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name)){
|
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (continent)
|
switch (continent)
|
||||||
{
|
{
|
||||||
@@ -421,8 +523,9 @@ namespace CarCareTracker.Helper
|
|||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
} else
|
}
|
||||||
{
|
else
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
|
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
|
||||||
@@ -435,7 +538,8 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
} catch (Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -606,7 +710,8 @@ namespace CarCareTracker.Helper
|
|||||||
if (input == 0M.ToString("C2") && hideZero)
|
if (input == 0M.ToString("C2") && hideZero)
|
||||||
{
|
{
|
||||||
return "---";
|
return "---";
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
|
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
|
||||||
}
|
}
|
||||||
@@ -622,6 +727,13 @@ namespace CarCareTracker.Helper
|
|||||||
return string.IsNullOrWhiteSpace(decorations) ? input.ToString("C2") : $"{input.ToString("C2")}{decorations}";
|
return string.IsNullOrWhiteSpace(decorations) ? input.ToString("C2") : $"{input.ToString("C2")}{decorations}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static JsonSerializerOptions GetInvariantOption()
|
||||||
|
{
|
||||||
|
var serializerOption = new JsonSerializerOptions();
|
||||||
|
serializerOption.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||||
|
serializerOption.Converters.Add(new InvariantConverter());
|
||||||
|
return serializerOption;
|
||||||
|
}
|
||||||
public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords)
|
public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords)
|
||||||
{
|
{
|
||||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||||
@@ -659,5 +771,52 @@ namespace CarCareTracker.Helper
|
|||||||
_csv.NextRecord();
|
_csv.NextRecord();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static byte[] RemindersToCalendar(List<ReminderRecordViewModel> reminders)
|
||||||
|
{
|
||||||
|
//converts reminders to iCal file
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
//start the calendar item
|
||||||
|
sb.AppendLine("BEGIN:VCALENDAR");
|
||||||
|
sb.AppendLine("VERSION:2.0");
|
||||||
|
sb.AppendLine("PRODID:lubelogger.com");
|
||||||
|
sb.AppendLine("CALSCALE:GREGORIAN");
|
||||||
|
sb.AppendLine("METHOD:PUBLISH");
|
||||||
|
|
||||||
|
//create events.
|
||||||
|
foreach(ReminderRecordViewModel reminder in reminders)
|
||||||
|
{
|
||||||
|
var dtStart = reminder.Date.Date.ToString("yyyyMMddTHHmm00");
|
||||||
|
var dtEnd = reminder.Date.Date.AddDays(1).AddMilliseconds(-1).ToString("yyyyMMddTHHmm00");
|
||||||
|
var calendarUID = new Guid(MD5.HashData(Encoding.UTF8.GetBytes($"{dtStart}_{reminder.Description}")));
|
||||||
|
sb.AppendLine("BEGIN:VEVENT");
|
||||||
|
sb.AppendLine("DTSTAMP:" + DateTime.Now.ToString("yyyyMMddTHHmm00"));
|
||||||
|
sb.AppendLine("UID:" + calendarUID);
|
||||||
|
sb.AppendLine("DTSTART:" + dtStart);
|
||||||
|
sb.AppendLine("DTEND:" + dtEnd);
|
||||||
|
sb.AppendLine($"SUMMARY:{reminder.Description}");
|
||||||
|
sb.AppendLine($"DESCRIPTION:{reminder.Description}");
|
||||||
|
switch (reminder.Urgency)
|
||||||
|
{
|
||||||
|
case ReminderUrgency.NotUrgent:
|
||||||
|
sb.AppendLine("PRIORITY:3");
|
||||||
|
break;
|
||||||
|
case ReminderUrgency.Urgent:
|
||||||
|
sb.AppendLine("PRIORITY:2");
|
||||||
|
break;
|
||||||
|
case ReminderUrgency.VeryUrgent:
|
||||||
|
sb.AppendLine("PRIORITY:1");
|
||||||
|
break;
|
||||||
|
case ReminderUrgency.PastDue:
|
||||||
|
sb.AppendLine("PRIORITY:1");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sb.AppendLine("END:VEVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
//end calendar item
|
||||||
|
sb.AppendLine("END:VCALENDAR");
|
||||||
|
string calendarContent = sb.ToString();
|
||||||
|
return Encoding.UTF8.GetBytes(calendarContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace CarCareTracker.Logic
|
|||||||
OperationResponse RequestResetPassword(LoginModel credentials);
|
OperationResponse RequestResetPassword(LoginModel credentials);
|
||||||
OperationResponse ResetPasswordByUser(LoginModel credentials);
|
OperationResponse ResetPasswordByUser(LoginModel credentials);
|
||||||
OperationResponse ResetUserPassword(LoginModel credentials);
|
OperationResponse ResetUserPassword(LoginModel credentials);
|
||||||
|
OperationResponse SendRegistrationToken(LoginModel credentials);
|
||||||
UserData ValidateUserCredentials(LoginModel credentials);
|
UserData ValidateUserCredentials(LoginModel credentials);
|
||||||
UserData ValidateOpenIDUser(LoginModel credentials);
|
UserData ValidateOpenIDUser(LoginModel credentials);
|
||||||
bool CheckIfUserIsValid(int userId);
|
bool CheckIfUserIsValid(int userId);
|
||||||
@@ -190,6 +191,16 @@ namespace CarCareTracker.Logic
|
|||||||
return OperationResponse.Failed();
|
return OperationResponse.Failed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public OperationResponse SendRegistrationToken(LoginModel credentials)
|
||||||
|
{
|
||||||
|
if (_configHelper.GetServerOpenRegistration())
|
||||||
|
{
|
||||||
|
return GenerateUserToken(credentials.EmailAddress, true);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return OperationResponse.Failed("Open Registration Disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a token and notifies user via email so they can reset their password.
|
/// Generates a token and notifies user via email so they can reset their password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -310,7 +321,18 @@ namespace CarCareTracker.Logic
|
|||||||
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
|
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
|
||||||
if (existingToken.Id != default)
|
if (existingToken.Id != default)
|
||||||
{
|
{
|
||||||
return OperationResponse.Failed("There is an existing token tied to this email address");
|
if (autoNotify) //re-send email
|
||||||
|
{
|
||||||
|
var notificationResult = _mailHelper.NotifyUserForRegistration(emailAddress, existingToken.Body);
|
||||||
|
if (notificationResult.Success)
|
||||||
|
{
|
||||||
|
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}, a new email has been sent out");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return notificationResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}");
|
||||||
}
|
}
|
||||||
var token = new Token()
|
var token = new Token()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.Controllers;
|
||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
using CarCareTracker.Helper;
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
@@ -12,11 +13,13 @@ namespace CarCareTracker.Logic
|
|||||||
int GetMaxMileage(VehicleRecords vehicleRecords);
|
int GetMaxMileage(VehicleRecords vehicleRecords);
|
||||||
int GetMinMileage(int vehicleId);
|
int GetMinMileage(int vehicleId);
|
||||||
int GetMinMileage(VehicleRecords vehicleRecords);
|
int GetMinMileage(VehicleRecords vehicleRecords);
|
||||||
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
|
int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
|
||||||
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
|
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
|
||||||
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
|
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
|
||||||
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
|
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
|
||||||
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
|
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
|
||||||
|
bool UpdateRecurringTaxes(int vehicleId);
|
||||||
|
void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription);
|
||||||
}
|
}
|
||||||
public class VehicleLogic: IVehicleLogic
|
public class VehicleLogic: IVehicleLogic
|
||||||
{
|
{
|
||||||
@@ -29,6 +32,10 @@ namespace CarCareTracker.Logic
|
|||||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
private readonly IPlanRecordDataAccess _planRecordDataAccess;
|
private readonly IPlanRecordDataAccess _planRecordDataAccess;
|
||||||
private readonly IReminderHelper _reminderHelper;
|
private readonly IReminderHelper _reminderHelper;
|
||||||
|
private readonly IVehicleDataAccess _dataAccess;
|
||||||
|
private readonly ISupplyRecordDataAccess _supplyRecordDataAccess;
|
||||||
|
private readonly ILogger<VehicleLogic> _logger;
|
||||||
|
|
||||||
public VehicleLogic(
|
public VehicleLogic(
|
||||||
IServiceRecordDataAccess serviceRecordDataAccess,
|
IServiceRecordDataAccess serviceRecordDataAccess,
|
||||||
IGasRecordDataAccess gasRecordDataAccess,
|
IGasRecordDataAccess gasRecordDataAccess,
|
||||||
@@ -38,7 +45,10 @@ namespace CarCareTracker.Logic
|
|||||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
IPlanRecordDataAccess planRecordDataAccess,
|
IPlanRecordDataAccess planRecordDataAccess,
|
||||||
IReminderHelper reminderHelper
|
IReminderHelper reminderHelper,
|
||||||
|
IVehicleDataAccess dataAccess,
|
||||||
|
ISupplyRecordDataAccess supplyRecordDataAccess,
|
||||||
|
ILogger<VehicleLogic> logger
|
||||||
) {
|
) {
|
||||||
_serviceRecordDataAccess = serviceRecordDataAccess;
|
_serviceRecordDataAccess = serviceRecordDataAccess;
|
||||||
_gasRecordDataAccess = gasRecordDataAccess;
|
_gasRecordDataAccess = gasRecordDataAccess;
|
||||||
@@ -49,6 +59,9 @@ namespace CarCareTracker.Logic
|
|||||||
_planRecordDataAccess = planRecordDataAccess;
|
_planRecordDataAccess = planRecordDataAccess;
|
||||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||||
_reminderHelper = reminderHelper;
|
_reminderHelper = reminderHelper;
|
||||||
|
_dataAccess = dataAccess;
|
||||||
|
_supplyRecordDataAccess = supplyRecordDataAccess;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
public VehicleRecords GetVehicleRecords(int vehicleId)
|
public VehicleRecords GetVehicleRecords(int vehicleId)
|
||||||
{
|
{
|
||||||
@@ -186,22 +199,44 @@ namespace CarCareTracker.Logic
|
|||||||
}
|
}
|
||||||
return numbersArray.Any() ? numbersArray.Min() : 0;
|
return numbersArray.Any() ? numbersArray.Min() : 0;
|
||||||
}
|
}
|
||||||
public int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
|
public int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
|
||||||
{
|
{
|
||||||
var startDate = DateTime.Now;
|
var startDate = DateTime.Now;
|
||||||
var endDate = DateTime.Now;
|
var endDate = DateTime.Now;
|
||||||
if (!string.IsNullOrWhiteSpace(soldDate))
|
bool usePurchaseDate = false;
|
||||||
|
bool useSoldDate = false;
|
||||||
|
if (!string.IsNullOrWhiteSpace(soldDate) && DateTime.TryParse(soldDate, out DateTime vehicleSoldDate))
|
||||||
{
|
{
|
||||||
endDate = DateTime.Parse(soldDate);
|
if (year == default || year >= vehicleSoldDate.Year) //All Time is selected or the selected year is greater or equal to the year the vehicle is sold
|
||||||
|
{
|
||||||
|
endDate = vehicleSoldDate; //cap end date to vehicle sold date.
|
||||||
|
useSoldDate = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(purchaseDate))
|
if (!string.IsNullOrWhiteSpace(purchaseDate) && DateTime.TryParse(purchaseDate, out DateTime vehiclePurchaseDate))
|
||||||
{
|
{
|
||||||
//if purchase date is provided, then we just have to subtract the begin date to end date and return number of months
|
if (year == default || year <= vehiclePurchaseDate.Year) //All Time is selected or the selected year is less or equal to the year the vehicle is purchased
|
||||||
startDate = DateTime.Parse(purchaseDate);
|
{
|
||||||
|
startDate = vehiclePurchaseDate; //cap start date to vehicle purchase date
|
||||||
|
usePurchaseDate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
var calendarYearStart = new DateTime(year, 1, 1);
|
||||||
|
var calendarYearEnd = new DateTime(year + 1, 1, 1);
|
||||||
|
if (!useSoldDate)
|
||||||
|
{
|
||||||
|
endDate = endDate > calendarYearEnd ? calendarYearEnd : endDate;
|
||||||
|
}
|
||||||
|
if (!usePurchaseDate)
|
||||||
|
{
|
||||||
|
startDate = startDate > calendarYearStart ? calendarYearStart : startDate;
|
||||||
|
}
|
||||||
var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays);
|
var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays);
|
||||||
return timeElapsed;
|
return timeElapsed;
|
||||||
}
|
}
|
||||||
var dateArray = new List<DateTime>();
|
var dateArray = new List<DateTime>() { startDate };
|
||||||
dateArray.AddRange(serviceRecords.Select(x => x.Date));
|
dateArray.AddRange(serviceRecords.Select(x => x.Date));
|
||||||
dateArray.AddRange(repairRecords.Select(x => x.Date));
|
dateArray.AddRange(repairRecords.Select(x => x.Date));
|
||||||
dateArray.AddRange(gasRecords.Select(x => x.Date));
|
dateArray.AddRange(gasRecords.Select(x => x.Date));
|
||||||
@@ -268,11 +303,11 @@ namespace CarCareTracker.Logic
|
|||||||
//set next reminder
|
//set next reminder
|
||||||
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
|
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
|
||||||
{
|
{
|
||||||
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) }).First();
|
||||||
}
|
}
|
||||||
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
|
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
|
||||||
{
|
{
|
||||||
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), Tags = string.Join(' ', x.Tags) }).First();
|
||||||
}
|
}
|
||||||
apiResult.Add(resultToAdd);
|
apiResult.Add(resultToAdd);
|
||||||
}
|
}
|
||||||
@@ -317,5 +352,98 @@ namespace CarCareTracker.Logic
|
|||||||
}
|
}
|
||||||
return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList();
|
return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList();
|
||||||
}
|
}
|
||||||
|
public bool UpdateRecurringTaxes(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
if (!string.IsNullOrWhiteSpace(vehicleData.SoldDate))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool RecurringTaxIsOutdated(TaxRecord taxRecord)
|
||||||
|
{
|
||||||
|
var monthInterval = taxRecord.RecurringInterval != ReminderMonthInterval.Other ? (int)taxRecord.RecurringInterval : taxRecord.CustomMonthInterval;
|
||||||
|
bool addDays = taxRecord.RecurringInterval == ReminderMonthInterval.Other && taxRecord.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
|
||||||
|
return addDays ? DateTime.Now > taxRecord.Date.AddDays(monthInterval) : DateTime.Now > taxRecord.Date.AddMonths(monthInterval);
|
||||||
|
}
|
||||||
|
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
var outdatedRecurringFees = result.Where(x => x.IsRecurring && RecurringTaxIsOutdated(x));
|
||||||
|
if (outdatedRecurringFees.Any())
|
||||||
|
{
|
||||||
|
var success = false;
|
||||||
|
foreach (TaxRecord recurringFee in outdatedRecurringFees)
|
||||||
|
{
|
||||||
|
var monthInterval = recurringFee.RecurringInterval != ReminderMonthInterval.Other ? (int)recurringFee.RecurringInterval : recurringFee.CustomMonthInterval;
|
||||||
|
bool isOutdated = true;
|
||||||
|
bool addDays = recurringFee.RecurringInterval == ReminderMonthInterval.Other && recurringFee.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
|
||||||
|
//update the original outdated tax record
|
||||||
|
recurringFee.IsRecurring = false;
|
||||||
|
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
||||||
|
//month multiplier for severely outdated monthly tax records.
|
||||||
|
int monthMultiplier = 1;
|
||||||
|
var originalDate = recurringFee.Date;
|
||||||
|
while (isOutdated)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
|
||||||
|
monthMultiplier++;
|
||||||
|
var nextnextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
|
||||||
|
recurringFee.Date = nextDate;
|
||||||
|
recurringFee.Id = default; //new record
|
||||||
|
recurringFee.IsRecurring = DateTime.Now <= nextnextDate;
|
||||||
|
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
||||||
|
isOutdated = !recurringFee.IsRecurring;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
isOutdated = false; //break out of loop if something broke.
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
return false; //no outdated recurring tax records.
|
||||||
|
}
|
||||||
|
public void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription)
|
||||||
|
{
|
||||||
|
foreach (SupplyUsageHistory supply in supplyUsage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (supply.Id == default)
|
||||||
|
{
|
||||||
|
continue; //no id, skip current supply.
|
||||||
|
}
|
||||||
|
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.Id);
|
||||||
|
if (result != null && result.Id != default)
|
||||||
|
{
|
||||||
|
//supply exists, re-add the quantity and cost
|
||||||
|
result.Quantity += supply.Quantity;
|
||||||
|
result.Cost += supply.Cost;
|
||||||
|
var requisitionRecord = new SupplyUsageHistory
|
||||||
|
{
|
||||||
|
Id = supply.Id,
|
||||||
|
Date = DateTime.Now.Date,
|
||||||
|
Description = $"Restored from {usageDescription}",
|
||||||
|
Quantity = supply.Quantity,
|
||||||
|
Cost = supply.Cost
|
||||||
|
};
|
||||||
|
result.RequisitionHistory.Add(requisitionRecord);
|
||||||
|
//save
|
||||||
|
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to find supply with id {supply.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error restoring supply with id {supply.Id} : {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ namespace CarCareTracker.Middleware
|
|||||||
var userIdentity = new List<Claim>
|
var userIdentity = new List<Claim>
|
||||||
{
|
{
|
||||||
new(ClaimTypes.Name, splitString[0]),
|
new(ClaimTypes.Name, splitString[0]),
|
||||||
new(ClaimTypes.NameIdentifier, userData.Id.ToString())
|
new(ClaimTypes.NameIdentifier, userData.Id.ToString()),
|
||||||
|
new(ClaimTypes.Email, userData.EmailAddress),
|
||||||
|
new(ClaimTypes.Role, "APIAuth")
|
||||||
};
|
};
|
||||||
if (userData.IsAdmin)
|
if (userData.IsAdmin)
|
||||||
{
|
{
|
||||||
@@ -154,6 +156,7 @@ namespace CarCareTracker.Middleware
|
|||||||
if (value.ToString().ToLower() == "api")
|
if (value.ToString().ToLower() == "api")
|
||||||
{
|
{
|
||||||
Response.StatusCode = 401;
|
Response.StatusCode = 401;
|
||||||
|
Response.Headers.Append("WWW-Authenticate", "Basic");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Models/API/ReleaseVersion.cs
Normal file
18
Models/API/ReleaseVersion.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// For deserializing GitHub response for latest version
|
||||||
|
/// </summary>
|
||||||
|
public class ReleaseResponse
|
||||||
|
{
|
||||||
|
public string tag_name { get; set; }
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// For returning the version numbers via API.
|
||||||
|
/// </summary>
|
||||||
|
public class ReleaseVersion
|
||||||
|
{
|
||||||
|
public string CurrentVersion { get; set; }
|
||||||
|
public string LatestVersion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
138
Models/API/TypeConverter.cs
Normal file
138
Models/API/TypeConverter.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class DummyType
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
class InvariantConverter : JsonConverter<DummyType>
|
||||||
|
{
|
||||||
|
public override void Write(Utf8JsonWriter writer, DummyType value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
public override DummyType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class FromDateOptional: JsonConverter<string>
|
||||||
|
{
|
||||||
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var tokenType = reader.TokenType;
|
||||||
|
if (tokenType == JsonTokenType.String)
|
||||||
|
{
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
else if (tokenType == JsonTokenType.Number)
|
||||||
|
{
|
||||||
|
if (reader.TryGetInt64(out long intInput))
|
||||||
|
{
|
||||||
|
return DateTimeOffset.FromUnixTimeSeconds(intInput).Date.ToShortDateString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(DateTime.Parse(value).ToString("yyyy-MM-dd"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class FromDecimalOptional : JsonConverter<string>
|
||||||
|
{
|
||||||
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var tokenType = reader.TokenType;
|
||||||
|
if (tokenType == JsonTokenType.String)
|
||||||
|
{
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
else if (tokenType == JsonTokenType.Number) {
|
||||||
|
if (reader.TryGetDecimal(out decimal decimalInput))
|
||||||
|
{
|
||||||
|
return decimalInput.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (options.Converters.Any(x=>x.Type == typeof(DummyType)))
|
||||||
|
{
|
||||||
|
writer.WriteNumberValue(decimal.Parse(value));
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class FromIntOptional : JsonConverter<string>
|
||||||
|
{
|
||||||
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var tokenType = reader.TokenType;
|
||||||
|
if (tokenType == JsonTokenType.String)
|
||||||
|
{
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
else if (tokenType == JsonTokenType.Number)
|
||||||
|
{
|
||||||
|
if (reader.TryGetInt32(out int intInput))
|
||||||
|
{
|
||||||
|
return intInput.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
|
||||||
|
{
|
||||||
|
writer.WriteNumberValue(int.Parse(value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class FromBoolOptional : JsonConverter<string>
|
||||||
|
{
|
||||||
|
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var tokenType = reader.TokenType;
|
||||||
|
switch (tokenType)
|
||||||
|
{
|
||||||
|
case JsonTokenType.String:
|
||||||
|
return reader.GetString();
|
||||||
|
case JsonTokenType.True:
|
||||||
|
return "True";
|
||||||
|
case JsonTokenType.False:
|
||||||
|
return "False";
|
||||||
|
default:
|
||||||
|
return reader.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
|
||||||
|
{
|
||||||
|
writer.WriteBooleanValue(bool.Parse(value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp); } }
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp) || (Mileage == default && !MissedFuelUp); } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,14 @@
|
|||||||
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;
|
||||||
public bool DisableRegularLogin { get; set; } = false;
|
public bool DisableRegularLogin { get; set; } = false;
|
||||||
public bool UsePKCE { get; set; } = false;
|
public bool UsePKCE { get; set; } = false;
|
||||||
public string LogOutURL { get; set; } = "";
|
public string LogOutURL { get; set; } = "";
|
||||||
|
public string UserInfoURL { get; set; } = "";
|
||||||
public string RemoteAuthURL { get {
|
public string RemoteAuthURL { get {
|
||||||
var redirectUrl = $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}";
|
var redirectUrl = $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}";
|
||||||
if (UsePKCE)
|
if (UsePKCE)
|
||||||
|
|||||||
@@ -3,5 +3,6 @@
|
|||||||
public class OpenIDResult
|
public class OpenIDResult
|
||||||
{
|
{
|
||||||
public string id_token { get; set; }
|
public string id_token { get; set; }
|
||||||
|
public string access_token { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
Models/OIDC/OpenIDUserInfo.cs
Normal file
7
Models/OIDC/OpenIDUserInfo.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class OpenIDUserInfo
|
||||||
|
{
|
||||||
|
public string email { get; set; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
|
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
|
||||||
public int CustomMileageInterval { get; set; } = 0;
|
public int CustomMileageInterval { get; set; } = 0;
|
||||||
public int CustomMonthInterval { get; set; } = 0;
|
public int CustomMonthInterval { get; set; } = 0;
|
||||||
|
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
|
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
|
||||||
public int CustomMileageInterval { get; set; } = 0;
|
public int CustomMileageInterval { get; set; } = 0;
|
||||||
public int CustomMonthInterval { get; set; } = 0;
|
public int CustomMonthInterval { get; set; } = 0;
|
||||||
|
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
ReminderMonthInterval = ReminderMonthInterval,
|
ReminderMonthInterval = ReminderMonthInterval,
|
||||||
CustomMileageInterval = CustomMileageInterval,
|
CustomMileageInterval = CustomMileageInterval,
|
||||||
CustomMonthInterval = CustomMonthInterval,
|
CustomMonthInterval = CustomMonthInterval,
|
||||||
|
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
|
||||||
Notes = Notes,
|
Notes = Notes,
|
||||||
Tags = Tags
|
Tags = Tags
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,5 +13,6 @@
|
|||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
|
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
Models/Report/ReportHeader.cs
Normal file
10
Models/Report/ReportHeader.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReportHeader
|
||||||
|
{
|
||||||
|
public int MaxOdometer { get; set; }
|
||||||
|
public int DistanceTraveled { get; set; }
|
||||||
|
public decimal TotalCost { get; set; }
|
||||||
|
public string AverageMPG { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,13 @@
|
|||||||
{
|
{
|
||||||
public class ReportParameter
|
public class ReportParameter
|
||||||
{
|
{
|
||||||
public List<string> VisibleColumns { get; set; } = new List<string>();
|
public List<string> VisibleColumns { get; set; } = new List<string>();
|
||||||
public List<string> ExtraFields { get; set; } = new List<string>();
|
public List<string> ExtraFields { get; set; } = new List<string>();
|
||||||
|
public TagFilter TagFilter { get; set; } = TagFilter.Exclude;
|
||||||
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
|
public bool FilterByDateRange { get; set; } = false;
|
||||||
|
public string StartDate { get; set; } = "";
|
||||||
|
public string EndDate { get; set; } = "";
|
||||||
|
public bool PrintIndividualRecords { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class ReportViewModel
|
public class ReportViewModel
|
||||||
{
|
{
|
||||||
|
public ReportHeader ReportHeaderForVehicle { get; set; } = new ReportHeader();
|
||||||
public List<CostForVehicleByMonth> CostForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
|
public List<CostForVehicleByMonth> CostForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
|
||||||
public MPGForVehicleByMonth FuelMileageForVehicleByMonth { get; set; } = new MPGForVehicleByMonth();
|
public MPGForVehicleByMonth FuelMileageForVehicleByMonth { get; set; } = new MPGForVehicleByMonth();
|
||||||
public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
|
public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
|
||||||
|
|||||||
@@ -17,5 +17,7 @@
|
|||||||
public decimal TotalDepreciation { get; set; }
|
public decimal TotalDepreciation { get; set; }
|
||||||
public decimal DepreciationPerDay { get; set; }
|
public decimal DepreciationPerDay { get; set; }
|
||||||
public decimal DepreciationPerMile { get; set; }
|
public decimal DepreciationPerMile { get; set; }
|
||||||
|
public string StartDate { get; set; }
|
||||||
|
public string EndDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace CarCareTracker.Models
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Import model used for importing records via CSV.
|
/// Import model used for importing records via CSV.
|
||||||
@@ -41,68 +43,115 @@
|
|||||||
public string Cost { get; set; }
|
public string Cost { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public string Tags { get; set; }
|
public string Tags { get; set; }
|
||||||
public List<ExtraField> ExtraFields { get; set; }
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
}
|
}
|
||||||
public class GenericRecordExportModel
|
public class GenericRecordExportModel
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string Date { get; set; }
|
public string Date { get; set; }
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
public string Odometer { get; set; }
|
public string Odometer { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDecimalOptional))]
|
||||||
public string Cost { get; set; }
|
public string Cost { get; set; }
|
||||||
public string Tags { get; set; }
|
public string Tags { get; set; }
|
||||||
public List<ExtraField> ExtraFields { get; set; }
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
}
|
}
|
||||||
public class OdometerRecordExportModel
|
public class OdometerRecordExportModel
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string Date { get; set; }
|
public string Date { get; set; }
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
public string InitialOdometer { get; set; }
|
public string InitialOdometer { get; set; }
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
public string Odometer { get; set; }
|
public string Odometer { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public string Tags { get; set; }
|
public string Tags { get; set; }
|
||||||
public List<ExtraField> ExtraFields { get; set; }
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
}
|
}
|
||||||
public class TaxRecordExportModel
|
public class TaxRecordExportModel
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string Date { get; set; }
|
public string Date { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDecimalOptional))]
|
||||||
public string Cost { get; set; }
|
public string Cost { get; set; }
|
||||||
public string Tags { get; set; }
|
public string Tags { get; set; }
|
||||||
public List<ExtraField> ExtraFields { get; set; }
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
}
|
}
|
||||||
public class GasRecordExportModel
|
public class GasRecordExportModel
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string Date { get; set; }
|
public string Date { get; set; }
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
public string Odometer { get; set; }
|
public string Odometer { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDecimalOptional))]
|
||||||
public string FuelConsumed { get; set; }
|
public string FuelConsumed { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDecimalOptional))]
|
||||||
public string Cost { get; set; }
|
public string Cost { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDecimalOptional))]
|
||||||
public string FuelEconomy { get; set; }
|
public string FuelEconomy { get; set; }
|
||||||
|
[JsonConverter(typeof(FromBoolOptional))]
|
||||||
public string IsFillToFull { get; set; }
|
public string IsFillToFull { get; set; }
|
||||||
|
[JsonConverter(typeof(FromBoolOptional))]
|
||||||
public string MissedFuelUp { get; set; }
|
public string MissedFuelUp { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public string Tags { get; set; }
|
public string Tags { get; set; }
|
||||||
public List<ExtraField> ExtraFields { get; set; }
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
}
|
}
|
||||||
public class ReminderExportModel
|
public class ReminderExportModel
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
|
public string Id { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Urgency { get; set; }
|
public string Urgency { get; set; }
|
||||||
public string Metric { get; set; }
|
public string Metric { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string DueDate { get; set; }
|
public string DueDate { get; set; }
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
public string DueOdometer { get; set; }
|
public string DueOdometer { get; set; }
|
||||||
|
public string Tags { get; set; }
|
||||||
}
|
}
|
||||||
public class PlanRecordExportModel
|
public class PlanRecordExportModel
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(FromIntOptional))]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string DateCreated { get; set; }
|
public string DateCreated { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDateOptional))]
|
||||||
public string DateModified { get; set; }
|
public string DateModified { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public string Priority { get; set; }
|
public string Priority { get; set; }
|
||||||
public string Progress { get; set; }
|
public string Progress { get; set; }
|
||||||
|
[JsonConverter(typeof(FromDecimalOptional))]
|
||||||
public string Cost { get; set; }
|
public string Cost { get; set; }
|
||||||
public List<ExtraField> ExtraFields { get; set; }
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
}
|
||||||
|
public class UserExportModel
|
||||||
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string EmailAddress { get; set; }
|
||||||
|
[JsonConverter(typeof(FromBoolOptional))]
|
||||||
|
public string IsAdmin { get; set; }
|
||||||
|
[JsonConverter(typeof(FromBoolOptional))]
|
||||||
|
public string IsRoot { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
Models/Shared/StickerViewModel.cs
Normal file
11
Models/Shared/StickerViewModel.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class StickerViewModel
|
||||||
|
{
|
||||||
|
public ImportMode RecordType { get; set; }
|
||||||
|
public Vehicle VehicleData { get; set; } = new Vehicle();
|
||||||
|
public List<ReminderRecord> ReminderRecords { get; set; } = new List<ReminderRecord>();
|
||||||
|
public List<GenericRecord> GenericRecords { get; set; } = new List<GenericRecord>();
|
||||||
|
public List<SupplyRecord> SupplyRecords { get; set; } = new List<SupplyRecord>();
|
||||||
|
}
|
||||||
|
}
|
||||||
253
Models/Shared/WebHookPayload.cs
Normal file
253
Models/Shared/WebHookPayload.cs
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// WebHookPayload Object
|
||||||
|
/// </summary>
|
||||||
|
public class WebHookPayloadBase
|
||||||
|
{
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
public string Timestamp
|
||||||
|
{
|
||||||
|
get { return DateTime.UtcNow.ToString("O"); }
|
||||||
|
}
|
||||||
|
public Dictionary<string, string> Data { get; set; } = new Dictionary<string, string>();
|
||||||
|
/// <summary>
|
||||||
|
/// Legacy attributes below
|
||||||
|
/// </summary>
|
||||||
|
public string VehicleId { get; set; } = "";
|
||||||
|
public string Username { get; set; } = "";
|
||||||
|
public string Action { get; set; } = "";
|
||||||
|
}
|
||||||
|
public class DiscordWebHook
|
||||||
|
{
|
||||||
|
public string Username { get { return "LubeLogger"; } }
|
||||||
|
[JsonPropertyName("avatar_url")]
|
||||||
|
public string AvatarUrl { get { return "https://hargata.github.io/hargata/lubelogger_logo_small.png"; } }
|
||||||
|
public string Content { get; set; } = "";
|
||||||
|
public static DiscordWebHook FromWebHookPayload(WebHookPayload webHookPayload)
|
||||||
|
{
|
||||||
|
return new DiscordWebHook
|
||||||
|
{
|
||||||
|
Content = webHookPayload.Action,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class WebHookPayload: WebHookPayloadBase
|
||||||
|
{
|
||||||
|
private static string GetFriendlyActionType(string actionType)
|
||||||
|
{
|
||||||
|
var actionTypeParts = actionType.Split('.');
|
||||||
|
if (actionTypeParts.Length == 2)
|
||||||
|
{
|
||||||
|
var recordType = actionTypeParts[0];
|
||||||
|
var recordAction = actionTypeParts[1];
|
||||||
|
switch (recordAction)
|
||||||
|
{
|
||||||
|
case "add":
|
||||||
|
recordAction = "Added";
|
||||||
|
break;
|
||||||
|
case "update":
|
||||||
|
recordAction = "Updated";
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
recordAction = "Deleted";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (recordType.ToLower().Contains("record"))
|
||||||
|
{
|
||||||
|
var cleanedRecordType = recordType.ToLower().Replace("record", "");
|
||||||
|
cleanedRecordType = $"{char.ToUpper(cleanedRecordType[0])}{cleanedRecordType.Substring(1)} Record";
|
||||||
|
recordType = cleanedRecordType;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
recordType = $"{char.ToUpper(recordType[0])}{recordType.Substring(1)}";
|
||||||
|
}
|
||||||
|
return $"{recordAction} {recordType}";
|
||||||
|
} else if (actionTypeParts.Length == 3)
|
||||||
|
{
|
||||||
|
var recordType = actionTypeParts[0];
|
||||||
|
var recordAction = actionTypeParts[1];
|
||||||
|
var thirdPart = actionTypeParts[2];
|
||||||
|
switch (recordAction)
|
||||||
|
{
|
||||||
|
case "add":
|
||||||
|
recordAction = "Added";
|
||||||
|
break;
|
||||||
|
case "update":
|
||||||
|
recordAction = "Updated";
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
recordAction = "Deleted";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (recordType.ToLower().Contains("record"))
|
||||||
|
{
|
||||||
|
var cleanedRecordType = recordType.ToLower().Replace("record", "");
|
||||||
|
cleanedRecordType = $"{char.ToUpper(cleanedRecordType[0])}{cleanedRecordType.Substring(1)} Record";
|
||||||
|
recordType = cleanedRecordType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recordType = $"{char.ToUpper(recordType[0])}{recordType.Substring(1)}";
|
||||||
|
}
|
||||||
|
if (thirdPart == "api")
|
||||||
|
{
|
||||||
|
return $"{recordAction} {recordType} via API";
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return $"{recordAction} {recordType}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromGenericRecord(GenericRecord genericRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("description", genericRecord.Description);
|
||||||
|
payloadDictionary.Add("odometer", genericRecord.Mileage.ToString());
|
||||||
|
payloadDictionary.Add("vehicleId", genericRecord.VehicleId.ToString());
|
||||||
|
payloadDictionary.Add("cost", genericRecord.Cost.ToString("F2"));
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = genericRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {genericRecord.Description}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromGasRecord(GasRecord gasRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("odometer", gasRecord.Mileage.ToString());
|
||||||
|
payloadDictionary.Add("fuelconsumed", gasRecord.Gallons.ToString());
|
||||||
|
payloadDictionary.Add("vehicleId", gasRecord.VehicleId.ToString());
|
||||||
|
payloadDictionary.Add("cost", gasRecord.Cost.ToString("F2"));
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = gasRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Odometer: {gasRecord.Mileage}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromOdometerRecord(OdometerRecord odometerRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("initialodometer", odometerRecord.InitialMileage.ToString());
|
||||||
|
payloadDictionary.Add("odometer", odometerRecord.Mileage.ToString());
|
||||||
|
payloadDictionary.Add("vehicleId", odometerRecord.VehicleId.ToString());
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = odometerRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Odometer: {odometerRecord.Mileage}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromTaxRecord(TaxRecord taxRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("description", taxRecord.Description);
|
||||||
|
payloadDictionary.Add("vehicleId", taxRecord.VehicleId.ToString());
|
||||||
|
payloadDictionary.Add("cost", taxRecord.Cost.ToString("F2"));
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = taxRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {taxRecord.Description}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromPlanRecord(PlanRecord planRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("description", planRecord.Description);
|
||||||
|
payloadDictionary.Add("vehicleId", planRecord.VehicleId.ToString());
|
||||||
|
payloadDictionary.Add("cost", planRecord.Cost.ToString("F2"));
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = planRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {planRecord.Description}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromReminderRecord(ReminderRecord reminderRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("description", reminderRecord.Description);
|
||||||
|
payloadDictionary.Add("vehicleId", reminderRecord.VehicleId.ToString());
|
||||||
|
payloadDictionary.Add("metric", reminderRecord.Metric.ToString());
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = reminderRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {reminderRecord.Description}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromSupplyRecord(SupplyRecord supplyRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("description", supplyRecord.Description);
|
||||||
|
payloadDictionary.Add("vehicleId", supplyRecord.VehicleId.ToString());
|
||||||
|
payloadDictionary.Add("cost", supplyRecord.Cost.ToString("F2"));
|
||||||
|
payloadDictionary.Add("quantity", supplyRecord.Quantity.ToString("F2"));
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = supplyRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {supplyRecord.Description}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload FromNoteRecord(Note noteRecord, string actionType, string userName)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
payloadDictionary.Add("description", noteRecord.Description);
|
||||||
|
payloadDictionary.Add("vehicleId", noteRecord.VehicleId.ToString());
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = noteRecord.VehicleId.ToString(),
|
||||||
|
Username = userName,
|
||||||
|
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {noteRecord.Description}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static WebHookPayload Generic(string payload, string actionType, string userName, string vehicleId)
|
||||||
|
{
|
||||||
|
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
|
||||||
|
payloadDictionary.Add("user", userName);
|
||||||
|
if (!string.IsNullOrWhiteSpace(payload))
|
||||||
|
{
|
||||||
|
payloadDictionary.Add("description", payload);
|
||||||
|
}
|
||||||
|
return new WebHookPayload
|
||||||
|
{
|
||||||
|
Type = actionType,
|
||||||
|
Data = payloadDictionary,
|
||||||
|
VehicleId = string.IsNullOrWhiteSpace(vehicleId) ? "N/A" : vehicleId,
|
||||||
|
Username = userName,
|
||||||
|
Action = string.IsNullOrWhiteSpace(payload) ? $"{userName} {GetFriendlyActionType(actionType)}" : $"{userName} {payload}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
public bool IsRecurring { get; set; } = false;
|
public bool IsRecurring { get; set; } = false;
|
||||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
|
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||||
public int CustomMonthInterval { get; set; } = 0;
|
public int CustomMonthInterval { get; set; } = 0;
|
||||||
|
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
public bool IsRecurring { get; set; } = false;
|
public bool IsRecurring { get; set; } = false;
|
||||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
|
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
|
||||||
public int CustomMonthInterval { get; set; } = 0;
|
public int CustomMonthInterval { get; set; } = 0;
|
||||||
|
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
IsRecurring = IsRecurring,
|
IsRecurring = IsRecurring,
|
||||||
RecurringInterval = RecurringInterval,
|
RecurringInterval = RecurringInterval,
|
||||||
CustomMonthInterval = CustomMonthInterval,
|
CustomMonthInterval = CustomMonthInterval,
|
||||||
|
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
|
||||||
Files = Files,
|
Files = Files,
|
||||||
Tags = Tags,
|
Tags = Tags,
|
||||||
ExtraFields = ExtraFields
|
ExtraFields = ExtraFields
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
{
|
{
|
||||||
public ImportMode Tab { get; set; }
|
public ImportMode Tab { get; set; }
|
||||||
public List<string> VisibleColumns { get; set; } = new List<string>();
|
public List<string> VisibleColumns { get; set; } = new List<string>();
|
||||||
|
public List<string> ColumnOrder { get; set; } = new List<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,9 @@
|
|||||||
public bool AutomaticDecimalFormat { get; set; }
|
public bool AutomaticDecimalFormat { get; set; }
|
||||||
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 ShowCalendar { get; set; }
|
||||||
|
public bool ShowVehicleThumbnail { 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>
|
||||||
|
|||||||
62
Program.cs
62
Program.cs
@@ -7,11 +7,14 @@ using Microsoft.AspNetCore.Authentication;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
//Print Messages
|
//Print Messages
|
||||||
StaticHelper.InitMessage(builder.Configuration);
|
StaticHelper.InitMessage(builder.Configuration);
|
||||||
|
//Check Migration
|
||||||
|
StaticHelper.CheckMigration(builder.Environment.WebRootPath, builder.Environment.ContentRootPath);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
@@ -66,8 +69,8 @@ builder.Services.AddSingleton<IGasHelper, GasHelper>();
|
|||||||
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
|
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
|
||||||
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
|
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
|
||||||
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
|
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
|
||||||
builder.Services.AddSingleton<IMailHelper, MailHelper>();
|
|
||||||
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
|
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
|
||||||
|
builder.Services.AddSingleton<IMailHelper, MailHelper>();
|
||||||
|
|
||||||
//configure logic
|
//configure logic
|
||||||
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
|
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
|
||||||
@@ -75,15 +78,6 @@ builder.Services.AddSingleton<IUserLogic, UserLogic>();
|
|||||||
builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>();
|
builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>();
|
||||||
builder.Services.AddSingleton<IVehicleLogic, VehicleLogic>();
|
builder.Services.AddSingleton<IVehicleLogic, VehicleLogic>();
|
||||||
|
|
||||||
if (!Directory.Exists("data"))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory("data");
|
|
||||||
}
|
|
||||||
if (!Directory.Exists("config"))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory("config");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Additional JsonFile
|
//Additional JsonFile
|
||||||
builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true);
|
builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
@@ -112,13 +106,57 @@ var app = builder.Build();
|
|||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
app.UseExceptionHandler("/Home/Error");
|
app.UseExceptionHandler("/Home/Error");
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
|
FileProvider = new PhysicalFileProvider(
|
||||||
|
Path.Combine(builder.Environment.ContentRootPath, "data", "images")),
|
||||||
|
RequestPath = "/images",
|
||||||
OnPrepareResponse = ctx =>
|
OnPrepareResponse = ctx =>
|
||||||
{
|
{
|
||||||
if (ctx.Context.Request.Path.StartsWithSegments("/images") || ctx.Context.Request.Path.StartsWithSegments("/documents"))
|
if (ctx.Context.Request.Path.StartsWithSegments("/images"))
|
||||||
{
|
{
|
||||||
ctx.Context.Response.Headers.Add("Cache-Control", "no-store");
|
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
|
||||||
|
if (!ctx.Context.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
ctx.Context.Response.Redirect("/Login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
|
FileProvider = new PhysicalFileProvider(
|
||||||
|
Path.Combine(builder.Environment.ContentRootPath, "data", "documents")),
|
||||||
|
RequestPath = "/documents",
|
||||||
|
OnPrepareResponse = ctx =>
|
||||||
|
{
|
||||||
|
if (ctx.Context.Request.Path.StartsWithSegments("/documents"))
|
||||||
|
{
|
||||||
|
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
|
||||||
|
if (!ctx.Context.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
ctx.Context.Response.Redirect("/Login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
|
FileProvider = new PhysicalFileProvider(
|
||||||
|
Path.Combine(builder.Environment.ContentRootPath, "data", "translations")),
|
||||||
|
RequestPath = "/translations"
|
||||||
|
});
|
||||||
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
|
FileProvider = new PhysicalFileProvider(
|
||||||
|
Path.Combine(builder.Environment.ContentRootPath, "data", "temp")),
|
||||||
|
RequestPath = "/temp",
|
||||||
|
OnPrepareResponse = ctx =>
|
||||||
|
{
|
||||||
|
if (ctx.Context.Request.Path.StartsWithSegments("/temp"))
|
||||||
|
{
|
||||||
|
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
|
||||||
if (!ctx.Context.User.Identity.IsAuthenticated)
|
if (!ctx.Context.User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
ctx.Context.Response.Redirect("/Login");
|
ctx.Context.Response.Redirect("/Login");
|
||||||
|
|||||||
@@ -28,9 +28,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable testable">
|
||||||
|
<code>/api/whoami</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns information for current user
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
No Params
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable testable">
|
||||||
|
<code>/api/version</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns current version of LubeLogger and checks for updates
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
CheckForUpdate(bool) - checks for update(optional)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable testable">
|
||||||
<code>/api/vehicles</code>
|
<code>/api/vehicles</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -42,9 +70,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable testable">
|
||||||
<code>/api/vehicle/info</code>
|
<code>/api/vehicle/info</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -56,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/adjustedodometer</code>
|
<code>/api/vehicle/adjustedodometer</code>
|
||||||
@@ -72,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/odometerrecords</code>
|
<code>/api/vehicle/odometerrecords</code>
|
||||||
@@ -86,7 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/odometerrecords/latest</code>
|
<code>/api/vehicle/odometerrecords/latest</code>
|
||||||
@@ -100,7 +128,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
<span class="badge bg-primary">POST</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/odometerrecords/add</code>
|
<code>/api/vehicle/odometerrecords/add</code>
|
||||||
@@ -118,12 +146,127 @@
|
|||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
tags - tags separated by space(optional)<br />
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/odometerrecords/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Odometer Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Odometer Record<br />
|
||||||
|
date - Date to be entered<br />
|
||||||
|
initialOdometer - Initial Odometer reading<br />
|
||||||
|
odometer - Odometer reading<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/odometerrecords/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Odometer Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Odometer Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/planrecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of plan records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-primary">POST</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/planrecords/add</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Adds Plan Record to the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
<br />
|
||||||
|
Body(form-data): {<br />
|
||||||
|
description - Description<br />
|
||||||
|
cost - Cost<br />
|
||||||
|
type - ServiceRecord/RepairRecord/UpgradeRecord<br />
|
||||||
|
priority - Low/Normal/Critical<br />
|
||||||
|
progress - Backlog/InProgress/Testing<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/planrecords/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Plan Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Plan Record<br />
|
||||||
|
description - Description<br />
|
||||||
|
cost - Cost<br />
|
||||||
|
type - ServiceRecord/RepairRecord/UpgradeRecord<br />
|
||||||
|
priority - Low/Normal/Critical<br />
|
||||||
|
progress - Backlog/InProgress/Testing<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/planrecords/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Plan Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Plan Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/servicerecords</code>
|
<code>/api/vehicle/servicerecords</code>
|
||||||
@@ -137,7 +280,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
<span class="badge bg-primary">POST</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/servicerecords/add</code>
|
<code>/api/vehicle/servicerecords/add</code>
|
||||||
@@ -156,12 +299,51 @@
|
|||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
tags - tags separated by space(optional)<br />
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/servicerecords/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Service Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Service Record<br />
|
||||||
|
date - Date to be entered<br />
|
||||||
|
odometer - Odometer reading<br />
|
||||||
|
description - Description<br />
|
||||||
|
cost - Cost<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/servicerecords/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Service Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Service Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/repairrecords</code>
|
<code>/api/vehicle/repairrecords</code>
|
||||||
@@ -175,7 +357,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
<span class="badge bg-primary">POST</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/repairrecords/add</code>
|
<code>/api/vehicle/repairrecords/add</code>
|
||||||
@@ -194,12 +376,51 @@
|
|||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
tags - tags separated by space(optional)<br />
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/repairrecords/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Repair Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Repair Record <br />
|
||||||
|
date - Date to be entered<br />
|
||||||
|
odometer - Odometer reading<br />
|
||||||
|
description - Description<br />
|
||||||
|
cost - Cost<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/repairrecords/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Repair Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Repair Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/upgraderecords</code>
|
<code>/api/vehicle/upgraderecords</code>
|
||||||
@@ -213,7 +434,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
<span class="badge bg-primary">POST</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/upgraderecords/add</code>
|
<code>/api/vehicle/upgraderecords/add</code>
|
||||||
@@ -232,12 +453,51 @@
|
|||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
tags - tags separated by space(optional)<br />
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/upgraderecords/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Upgrade Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Upgrade Record<br />
|
||||||
|
date - Date to be entered<br />
|
||||||
|
odometer - Odometer reading<br />
|
||||||
|
description - Description<br />
|
||||||
|
cost - Cost<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/upgraderecords/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Upgrade Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Upgrade Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/taxrecords</code>
|
<code>/api/vehicle/taxrecords</code>
|
||||||
@@ -251,7 +511,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
<span class="badge bg-success">GET</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable testable">
|
||||||
|
<code>/api/vehicle/taxrecords/check</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Outdated Recurring Tax Records
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
No Params
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-primary">POST</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/taxrecords/add</code>
|
<code>/api/vehicle/taxrecords/add</code>
|
||||||
@@ -269,12 +543,50 @@
|
|||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
tags - tags separated by space(optional)<br />
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/taxrecords/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Tax Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Tax Record<br />
|
||||||
|
date - Date to be entered<br />
|
||||||
|
description - Description<br />
|
||||||
|
cost - Cost<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/taxrecords/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Tax Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Tax Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/gasrecords</code>
|
<code>/api/vehicle/gasrecords</code>
|
||||||
@@ -292,7 +604,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
<span class="badge bg-primary">POST</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/gasrecords/add</code>
|
<code>/api/vehicle/gasrecords/add</code>
|
||||||
@@ -313,12 +625,53 @@
|
|||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
tags - tags separated by space(optional)<br />
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/gasrecords/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Gas Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Gas Record<br />
|
||||||
|
date - Date to be entered<br />
|
||||||
|
odometer - Odometer reading<br />
|
||||||
|
fuelConsumed - Fuel Consumed<br />
|
||||||
|
cost - Cost<br />
|
||||||
|
isFillToFull(bool) - Filled To Full<br />
|
||||||
|
missedFuelUp(bool) - Missed Fuel Up<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
|
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/gasrecords/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Gas Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Gas Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/reminders</code>
|
<code>/api/vehicle/reminders</code>
|
||||||
@@ -330,13 +683,102 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-primary">POST</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/reminders/add</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Adds Reminder Record to the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
<br />
|
||||||
|
Body(form-data): {<br />
|
||||||
|
description - Description<br />
|
||||||
|
dueDate - Due Date<br />
|
||||||
|
dueOdometer - Due Odometer reading<br />
|
||||||
|
metric - Date/Odometer/Both<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-warning">PUT</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/reminders/update</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Updates Reminder Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
Id - Id of Reminder Record<br />
|
||||||
|
description - Description<br />
|
||||||
|
dueDate - Due Date<br />
|
||||||
|
dueOdometer - Due Odometer reading<br />
|
||||||
|
metric - Date/Odometer/Both<br />
|
||||||
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge text-bg-danger">DELETE</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/vehicle/reminders/delete</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Deletes Reminder Record
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Id - Id of Reminder Record
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-success">GET</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable testable">
|
||||||
|
<code>/api/calendar</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns reminder calendar in ICS format
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
No Params
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row api-method">
|
||||||
|
<div class="col-1">
|
||||||
|
<span class="badge bg-primary">POST</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-5 copyable">
|
||||||
|
<code>/api/documents/upload</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Upload Documents
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Body(form-data): {<br />
|
||||||
|
documents[] - Files to Upload<br />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
{
|
{
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable testable">
|
||||||
<code>/api/vehicle/reminders/send</code>
|
<code>/api/vehicle/reminders/send</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -344,14 +786,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
(must be root user)<br />
|
(must be root user)<br />
|
||||||
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
|
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue](optional)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable testable">
|
||||||
<code>/api/makebackup</code>
|
<code>/api/makebackup</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -363,9 +805,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row api-method">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
<span class="badge bg-success">GET</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5 copyable">
|
<div class="col-5 copyable testable">
|
||||||
<code>/api/cleanup</code>
|
<code>/api/cleanup</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -373,13 +815,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
(must be root user)<br />
|
(must be root user)<br />
|
||||||
deepClean(bool) - Perform deep clean
|
deepClean(bool) - Perform deep clean(optional)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$('.copyable').on('click', function (e) {
|
$('.copyable').on('click', function (e) {
|
||||||
copyToClipboard(e.currentTarget);
|
if (e.ctrlKey || e.metaKey){
|
||||||
|
let targetElement = $(e.currentTarget);
|
||||||
|
if (targetElement.hasClass("testable")){
|
||||||
|
window.location = targetElement.text().trim();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
copyToClipboard(e.currentTarget);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
function showExtraFieldsInfo(){
|
function showExtraFieldsInfo(){
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
@@ -388,4 +837,11 @@
|
|||||||
icon: "info"
|
icon: "info"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function showAttachmentsInfo(){
|
||||||
|
Swal.fire({
|
||||||
|
title: "Attaching Files",
|
||||||
|
html: "The Document Upload Endpoint will upload the files and provide a formatted output which you can pass into this method",
|
||||||
|
icon: "info"
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -13,17 +13,21 @@
|
|||||||
emailServerIsSetup = false;
|
emailServerIsSetup = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@section Nav {
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="d-flex lubelogger-navbar">
|
||||||
|
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
|
||||||
|
<img src="@config.GetSmallLogoUrl()" class="lubelogger-logo" />
|
||||||
|
</div>
|
||||||
|
<span class="text-truncate lead">@translator.Translate(userLanguage, "Admin Panel")</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@model AdminViewModel
|
@model AdminViewModel
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row d-flex align-items-center justify-content-between justify-content-md-start">
|
|
||||||
<div class="col-2 col-md-1">
|
|
||||||
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 col-md-7 text-end text-md-start">
|
|
||||||
<span class="display-6">@translator.Translate(userLanguage, "Admin Panel")</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -56,23 +60,23 @@
|
|||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
|
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center ms-auto">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button onclick="generateNewToken()" class="btn btn-primary btn-md mt-1 mb-1">
|
||||||
|
<i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Generate")
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-primary btn-md mt-1 mb-1" @(emailServerIsSetup ? "" : "disabled") onclick="toggleAutoNotify(event)">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="enableAutoNotify" @(emailServerIsSetup ? "checked" : "disabled")>
|
||||||
|
<label class="form-check-label" for="enableAutoNotify">@translator.Translate(userLanguage, "Notify")</label>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center ms-auto">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button onclick="generateNewToken()" class="btn btn-primary btn-md mt-1 mb-1">
|
|
||||||
<i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Generate")
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-outline-primary btn-md mt-1 mb-1" @(emailServerIsSetup ? "" : "disabled") onclick="toggleAutoNotify(event)">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="enableAutoNotify" @(emailServerIsSetup ? "checked" : "disabled")>
|
|
||||||
<label class="form-check-label" for="enableAutoNotify">@translator.Translate(userLanguage, "Notify")</label>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-close btn-md mt-1 mb-1 ms-2" onclick="hideTokenModal()" aria-label="Close"></button>
|
<button class="btn btn-close btn-md mt-1 mb-1 ms-2" onclick="hideTokenModal()" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
|
|||||||
@@ -16,6 +16,71 @@
|
|||||||
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
|
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/lib/drawdown/drawdown.js"></script>
|
<script src="~/lib/drawdown/drawdown.js"></script>
|
||||||
}
|
}
|
||||||
|
@section Nav {
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="d-flex lubelogger-navbar">
|
||||||
|
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
|
||||||
|
<img src="@config.GetSmallLogoUrl()" class="lubelogger-logo lubelogger-tab" />
|
||||||
|
<img src="@config.GetLogoUrl()" class="lubelogger-logo lubelogger-mobile-nav-show" />
|
||||||
|
</div>
|
||||||
|
<ul class="nav nav-tabs lubelogger-tab flex-grow-1" id="homeTab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link resizable-nav-link @(Model == "garage" ? "active" : "")" oncontextmenu="sortGarage(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Garage")</span></button>
|
||||||
|
</li>
|
||||||
|
@if (config.GetServerEnableShopSupplies())
|
||||||
|
{
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
@if (userConfig.ShowCalendar)
|
||||||
|
{
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
@if (User.IsInRole("CookieAuth") || User.IsInRole("APIAuth"))
|
||||||
|
{
|
||||||
|
<li class="nav-item dropdown" role="presentation">
|
||||||
|
<a class="nav-link resizable-nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person"></i><span class="ms-2 d-sm-none d-md-inline">@User.Identity.Name</span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
||||||
|
{
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage, "Admin Panel")</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item" onclick="showRootAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item" onclick="showAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage, "Logout")</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<div class="lubelogger-navbar-button">
|
||||||
|
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
||||||
<ul class="navbar-nav" id="homeTab" role="tablist">
|
<ul class="navbar-nav" id="homeTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
@@ -27,13 +92,15 @@
|
|||||||
<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>
|
||||||
@if (User.IsInRole("CookieAuth"))
|
@if (User.IsInRole("CookieAuth") || User.IsInRole("APIAuth"))
|
||||||
{
|
{
|
||||||
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
||||||
{
|
{
|
||||||
@@ -59,60 +126,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mt-2">
|
|
||||||
<div class="d-flex lubelogger-navbar">
|
|
||||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
|
||||||
<div class="lubelogger-navbar-button">
|
|
||||||
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<ul class="nav nav-tabs lubelogger-tab" id="homeTab" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link resizable-nav-link @(Model == "garage" ? "active" : "")" oncontextmenu="sortGarage(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Garage")</span></button>
|
|
||||||
</li>
|
|
||||||
@if (config.GetServerEnableShopSupplies())
|
|
||||||
{
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<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 class="nav-item" role="presentation">
|
|
||||||
<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">
|
|
||||||
<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>
|
|
||||||
@if (User.IsInRole("CookieAuth"))
|
|
||||||
{
|
|
||||||
<li class="nav-item dropdown" role="presentation">
|
|
||||||
<a class="nav-link resizable-nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person"></i><span class="ms-2 d-sm-none d-md-inline">@User.Identity.Name</span></a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
|
||||||
{
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
|
||||||
{
|
|
||||||
<li>
|
|
||||||
<button class="dropdown-item" onclick="showRootAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
|
|
||||||
</li>
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
<li>
|
|
||||||
<button class="dropdown-item" onclick="showAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
<li>
|
|
||||||
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content" id="homeTab">
|
<div class="tab-content" id="homeTab">
|
||||||
<div class="tab-pane fade @(Model == "garage" ? "show active" : "")" id="garage-tab-pane" role="tabpanel" tabindex="0">
|
<div class="tab-pane fade @(Model == "garage" ? "show active" : "")" id="garage-tab-pane" role="tabpanel" tabindex="0">
|
||||||
<div id="garageContainer">
|
<div id="garageContainer">
|
||||||
@@ -146,6 +159,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stickerPrintContainer hideOnPrint">
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
loadGarage();
|
loadGarage();
|
||||||
bindWindowResize();
|
bindWindowResize();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
@if (recordTags.Any())
|
@if (recordTags.Any())
|
||||||
{
|
{
|
||||||
<div class='row'>
|
<div class='row'>
|
||||||
<div class="d-flex align-items-center flex-wrap mt-4">
|
<div class="col-12 d-flex align-items-center flex-wrap mt-2 mb-2">
|
||||||
@foreach (string recordTag in recordTags)
|
@foreach (string recordTag in recordTags)
|
||||||
{
|
{
|
||||||
<span onclick="filterGarage(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
<span onclick="filterGarage(this)" class="user-select-none ms-1 me-1 mt-1 mb-1 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||||
}
|
}
|
||||||
<datalist id="tagList">
|
<datalist id="tagList">
|
||||||
@foreach (string recordTag in recordTags)
|
@foreach (string recordTag in recordTags)
|
||||||
@@ -24,13 +24,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row">
|
<div class="row gy-3 align-items-stretch vehiclesContainer pb-2 @(recordTags.Any() ? "" : "mt-2")">
|
||||||
<div class="row gy-3 align-items-stretch vehiclesContainer">
|
|
||||||
@foreach (VehicleViewModel vehicle in Model)
|
@foreach (VehicleViewModel vehicle in Model)
|
||||||
{
|
{
|
||||||
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
|
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
|
||||||
{
|
{
|
||||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
|
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
|
||||||
<div class="card" onclick="viewVehicle(@vehicle.Id)">
|
<div class="card" onclick="viewVehicle(@vehicle.Id)">
|
||||||
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
|
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
|
||||||
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
|
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
|
||||||
@@ -82,10 +81,9 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 garage-item-add">
|
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 garage-item-add user-select-none">
|
||||||
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
||||||
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
|
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;pointer-events:none;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
227
Views/Home/_ServerConfig.cshtml
Normal file
227
Views/Home/_ServerConfig.cshtml
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
@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="inputOIDCUserInfo">@translator.Translate(userLanguage, "OIDC UserInfo URL")</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" readonly id="inputOIDCUserInfo" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.UserInfoURL">
|
||||||
|
</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>
|
||||||
@@ -47,9 +47,15 @@
|
|||||||
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
|
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div>
|
||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
|
<div class="form-check form-switch form-check-inline">
|
||||||
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
|
||||||
|
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUnitForFuelCost" checked="@Model.UserConfig.UseUnitForFuelCost">
|
||||||
|
<label class="form-check-label" for="useUnitForFuelCost">@translator.Translate(userLanguage, "Default to Fuel Unit Cost")</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption">
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption">
|
||||||
@@ -80,6 +86,14 @@
|
|||||||
<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 class="form-check form-switch form-check-inline">
|
||||||
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="showVehicleThumbnail" checked="@Model.UserConfig.ShowVehicleThumbnail">
|
||||||
|
<label class="form-check-label" for="showCalendar">@translator.Translate(userLanguage, "Show Vehicle Thumbnail in Header")</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
{
|
{
|
||||||
@@ -247,7 +261,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>
|
||||||
@@ -349,6 +370,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>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@inject ITranslationHelper translator
|
@inject ITranslationHelper translator
|
||||||
@{
|
@{
|
||||||
var userLanguage = config.GetServerLanguage();
|
var userLanguage = config.GetServerLanguage();
|
||||||
|
var openRegistrationEnabled = config.GetServerOpenRegistration();
|
||||||
}
|
}
|
||||||
@model string
|
@model string
|
||||||
@{
|
@{
|
||||||
@@ -17,11 +18,23 @@
|
|||||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||||
<input type="text" id="inputToken" class="form-control">
|
@if (openRegistrationEnabled)
|
||||||
|
{
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="inputToken" class="form-control">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendOpenIdRegistrationToken()"><i class="bi bi-send"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="text" id="inputToken" class="form-control">
|
||||||
|
}
|
||||||
</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>
|
||||||
@@ -46,4 +59,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function sendOpenIdRegistrationToken(){
|
||||||
|
var userEmail = decodeHTMLEntities('@Model');
|
||||||
|
$.post('/Login/SendRegistrationToken', { emailAddress: userEmail }, function (data) {
|
||||||
|
if (data.success) {
|
||||||
|
successToast(data.message);
|
||||||
|
} else {
|
||||||
|
errorToast(data.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
@inject ITranslationHelper translator
|
@inject ITranslationHelper translator
|
||||||
@{
|
@{
|
||||||
var userLanguage = config.GetServerLanguage();
|
var userLanguage = config.GetServerLanguage();
|
||||||
|
var openRegistrationEnabled = config.GetServerOpenRegistration();
|
||||||
}
|
}
|
||||||
|
@model LoginModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Register";
|
ViewData["Title"] = "Register";
|
||||||
}
|
}
|
||||||
@@ -16,11 +18,20 @@
|
|||||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||||
<input type="text" id="inputToken" class="form-control">
|
@if (openRegistrationEnabled) {
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendRegistrationToken()"><i class="bi bi-send"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
|
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
|
||||||
<input type="text" id="inputEmail" class="form-control">
|
<input type="text" id="inputEmail" class="form-control" value="@Model.EmailAddress">
|
||||||
</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>
|
||||||
@@ -29,7 +40,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>
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
@{
|
@{
|
||||||
var userLanguage = config.GetServerLanguage();
|
var userLanguage = config.GetServerLanguage();
|
||||||
}
|
}
|
||||||
|
@model LoginModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Reset Password";
|
ViewData["Title"] = "Reset Password";
|
||||||
}
|
}
|
||||||
@@ -16,16 +17,16 @@
|
|||||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||||
<input type="text" id="inputToken" class="form-control">
|
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
|
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
|
||||||
<input type="text" id="inputEmail" class="form-control">
|
<input type="text" id="inputEmail" class="form-control" value="@Model.EmailAddress">
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
<title>@ViewData["Title"] - LubeLogger</title>
|
<title>@ViewData["Title"] - LubeLogger</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="~/favicon.ico">
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" />
|
||||||
@@ -160,6 +161,7 @@
|
|||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@await RenderSectionAsync("Nav", required: false)
|
||||||
<div class="container" style="height:85vh;">
|
<div class="container" style="height:85vh;">
|
||||||
<main role="main">
|
<main role="main">
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
|
|||||||
@@ -25,6 +25,61 @@
|
|||||||
<script src="~/lib/chart-js/chart.umd.js"></script>
|
<script src="~/lib/chart-js/chart.umd.js"></script>
|
||||||
<script src="~/lib/drawdown/drawdown.js"></script>
|
<script src="~/lib/drawdown/drawdown.js"></script>
|
||||||
}
|
}
|
||||||
|
@section Nav{
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="d-flex lubelogger-navbar">
|
||||||
|
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
|
||||||
|
@if(userConfig.ShowVehicleThumbnail) {
|
||||||
|
<img src="@Model.ImageLocation" class="lubelogger-vehicle-logo @(string.IsNullOrWhiteSpace(Model.SoldDate) ? "" : "sold")" />
|
||||||
|
} else {
|
||||||
|
<img src="@config.GetSmallLogoUrl()" class="lubelogger-logo" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<ul class="nav nav-tabs lubelogger-tab flex-grow-1" id="vehicleTab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.Dashboard)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Dashboard")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.PlanRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-bar-chart-steps"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Planner")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.OdometerRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-speedometer"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Odometer")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ServiceRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Service Records")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.RepairRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Repairs")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.UpgradeRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-wrench-adjustable"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Upgrades")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.GasRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Fuel")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.SupplyRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" 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 class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.TaxRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Taxes")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Notes")</span></button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
|
||||||
|
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell"></i></div><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Reminders")</span></button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<span style="cursor:pointer;" onclick="editVehicle(@Model.Id)" class="text-truncate"><span class="lead">@($"{Model.Year} {Model.Make} {Model.Model}")<small class="text-body-secondary">@($"(#{StaticHelper.GetVehicleIdentifier(Model)})")</small></span><span class="ms-2 lubelogger-tab"><i class="bi bi-pencil-square"></i></span></span>
|
||||||
|
<div class="lubelogger-navbar-button">
|
||||||
|
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
||||||
<ul class="nav navbar-nav" id="vehicleTab" role="tablist">
|
<ul class="nav navbar-nav" id="vehicleTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation" style="order: -2">
|
<li class="nav-item" role="presentation" style="order: -2">
|
||||||
@@ -69,52 +124,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<button onclick="returnToGarage()" class="lubelogger-tab btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></button>
|
|
||||||
<h1 class="text-truncate display-4">@($"{Model.Year} {Model.Make} {Model.Model}")<small class="text-body-secondary">@($"(#{StaticHelper.GetVehicleIdentifier(Model)})")</small></h1>
|
|
||||||
<button onclick="editVehicle(@Model.Id)" class="lubelogger-tab btn btn-warning btn-md mt-1 mb-1"><i class="bi bi-pencil-square"></i></button>
|
|
||||||
<div class="lubelogger-navbar-button">
|
|
||||||
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<ul class="nav nav-tabs lubelogger-tab" id="vehicleTab" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.Dashboard)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Dashboard")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.PlanRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-bar-chart-steps"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Planner")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.OdometerRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-speedometer"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Odometer")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ServiceRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Service Records")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.RepairRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Repairs")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.UpgradeRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-wrench-adjustable"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Upgrades")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.GasRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Fuel")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.SupplyRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" 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 class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.TaxRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Taxes")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Notes")</span></button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
|
|
||||||
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell"></i></div><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Reminders")</span></button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content" id="vehicleTabContent">
|
<div class="tab-content" id="vehicleTabContent">
|
||||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.GasRecord)" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.GasRecord)" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
@@ -158,6 +167,8 @@
|
|||||||
<div class="modal-content" id="inputSuppliesModalContent"></div>
|
<div class="modal-content" id="inputSuppliesModalContent"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stickerPrintContainer hideOnPrint">
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function GetVehicleId() {
|
function GetVehicleId() {
|
||||||
return {
|
return {
|
||||||
@@ -173,4 +184,5 @@
|
|||||||
return { tab: "@userConfig.DefaultTab" };
|
return { tab: "@userConfig.DefaultTab" };
|
||||||
}
|
}
|
||||||
bindWindowResize();
|
bindWindowResize();
|
||||||
|
checkRecurringTaxes();
|
||||||
</script>
|
</script>
|
||||||
9
Views/Vehicle/_AttachmentColumn.cshtml
Normal file
9
Views/Vehicle/_AttachmentColumn.cshtml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@model List<UploadedFiles>
|
||||||
|
@if (Model.Any()){
|
||||||
|
<span class="position-relative">
|
||||||
|
<i class="bi bi-paperclip"></i>
|
||||||
|
<span class="position-absolute attachment-badge-xs start-100 translate-middle badge rounded-pill bg-secondary">
|
||||||
|
@Model.Count()
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
@@ -21,30 +21,7 @@
|
|||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
@translator.Translate(userLanguage, "Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.")
|
@translator.Translate(userLanguage, "Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.")
|
||||||
</div>
|
</div>
|
||||||
@if (Model == ImportMode.GasRecord)
|
<a class="btn btn-link" href="@($"/Vehicle/GenerateCsvSample?mode={Model.ToString()}")" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
||||||
{
|
|
||||||
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
|
||||||
}
|
|
||||||
else if (Model == ImportMode.ServiceRecord || Model == ImportMode.RepairRecord || Model == ImportMode.UpgradeRecord)
|
|
||||||
{
|
|
||||||
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
|
||||||
}
|
|
||||||
else if (Model == ImportMode.TaxRecord)
|
|
||||||
{
|
|
||||||
<a class="btn btn-link" href="/defaults/taxrecordsample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
|
||||||
}
|
|
||||||
else if (Model == ImportMode.SupplyRecord)
|
|
||||||
{
|
|
||||||
<a class="btn btn-link" href="/defaults/supplysample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
|
||||||
}
|
|
||||||
else if (Model == ImportMode.PlanRecord)
|
|
||||||
{
|
|
||||||
<a class="btn btn-link" href="/defaults/plansample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
|
||||||
}
|
|
||||||
else if (Model == ImportMode.OdometerRecord)
|
|
||||||
{
|
|
||||||
<a class="btn btn-link" href="/defaults/odometersample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -54,31 +54,37 @@
|
|||||||
</li>
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Date" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Description" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Description" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Cost" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Attachment">
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Notes" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
<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>
|
||||||
@@ -87,7 +93,7 @@
|
|||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
var elementId = Guid.NewGuid();
|
var elementId = Guid.NewGuid();
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="@elementId">
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="@elementId">
|
||||||
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
@@ -118,6 +124,7 @@
|
|||||||
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
|
||||||
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||||
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||||
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
|
||||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
@@ -133,6 +140,7 @@
|
|||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(collisionRecord.Mileage == default ? "---" : collisionRecord.Mileage.ToString())</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(collisionRecord.Mileage == default ? "---" : collisionRecord.Mileage.ToString())</td>
|
||||||
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@collisionRecord.Description</td>
|
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@collisionRecord.Description</td>
|
||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(collisionRecord.Cost, hideZero))</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(collisionRecord.Cost, hideZero))</td>
|
||||||
|
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", collisionRecord.Files)</td>
|
||||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
@@ -183,6 +191,10 @@
|
|||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
||||||
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>
|
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>
|
||||||
|
|||||||
@@ -49,10 +49,10 @@
|
|||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}")}")</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}")}")</td>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</tr>
|
}
|
||||||
}
|
</tr>
|
||||||
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr class="d-flex fw-bold">
|
<tr class="d-flex fw-bold">
|
||||||
@@ -65,9 +65,11 @@
|
|||||||
var yearDataToDisplay = Model.CostData.Where(x => x.Year == year);
|
var yearDataToDisplay = Model.CostData.Where(x => x.Year == year);
|
||||||
if (yearDataToDisplay != null && yearDataToDisplay != default)
|
if (yearDataToDisplay != null && yearDataToDisplay != default)
|
||||||
{
|
{
|
||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(yearDataToDisplay.Sum(x => x.Cost).ToString("C2"), hideZero))</td>
|
var distanceTraveled = yearDataToDisplay.Sum(x => x.DistanceTraveled);
|
||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(yearDataToDisplay.Sum(x => x.DistanceTraveled) != default ? $"{yearDataToDisplay.Sum(x => x.DistanceTraveled).ToString("N0")} {Model.DistanceUnit}" : "---")</td>
|
var costAccrued = yearDataToDisplay.Sum(x => x.Cost);
|
||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(yearDataToDisplay.Sum(x => x.CostPerDistanceTraveled).ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(costAccrued.ToString("C2"), hideZero))</td>
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(distanceTraveled != default ? $"{distanceTraveled.ToString("N0")} {Model.DistanceUnit}" : "---")</td>
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(distanceTraveled != default && costAccrued != default ? (costAccrued / distanceTraveled).ToString("C2") : 0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Type")</th>
|
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Type")</th>
|
||||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")</th>
|
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")<span class="cost-table-hint d-none">@($"({Model.NumberOfDays.ToString("N0")})")</span></th>
|
||||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)</th>
|
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)<span class="cost-table-hint d-none">@($"({Model.TotalDistance.ToString("N0")})")</span></th>
|
||||||
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</th>
|
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
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>
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Views/Vehicle/_ExtraFieldMultiple.cshtml
Normal file
49
Views/Vehicle/_ExtraFieldMultiple.cshtml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
@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" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Number):
|
||||||
|
<input type="number" inputmode="numeric" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Decimal):
|
||||||
|
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Date):
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<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" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
break;
|
||||||
|
case (ExtraFieldType.Location):
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<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" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
{
|
{
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" title="@filesUploaded.Name" target="_blank">@filesUploaded.Name</a>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
|
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage, "Min Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage, "Min Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage, "Max Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage, "Max Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
}
|
}
|
||||||
|
<span class="ms-2 badge bg-success" id="totalDistanceLabel">@($"{translator.Translate(userLanguage, "Total Distance")}: {Model.GasRecords.Sum(x => x.DeltaMileage).ToString() ?? "0"} {distanceUnit}")</span>
|
||||||
}
|
}
|
||||||
<span class="ms-2 badge bg-success" id="totalFuelConsumedLabel">@($"{translator.Translate(userLanguage, "Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
<span class="ms-2 badge bg-success" id="totalFuelConsumedLabel">@($"{translator.Translate(userLanguage, "Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
||||||
<span class="ms-2 badge bg-success" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total Cost")}: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
|
<span class="ms-2 badge bg-success" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total Cost")}: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
|
||||||
@@ -103,49 +104,55 @@
|
|||||||
</li>
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='daterefueled' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_DateRefueled" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='daterefueled' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_DateRefueled" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_DateRefueled">@translator.Translate(userLanguage, "Date Refueled")</label>
|
<label class="form-check-label stretched-link" for="chkCol_DateRefueled">@translator.Translate(userLanguage, "Date Refueled")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='delta' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Delta" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='delta' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Delta" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Delta">@translator.Translate(userLanguage, "Delta")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Delta">@translator.Translate(userLanguage, "Delta")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='consumption' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Consumption" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='consumption' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Consumption" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Consumption">@translator.Translate(userLanguage, "Consumption")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Consumption">@translator.Translate(userLanguage, "Consumption")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='fueleconomy' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_FuelEconomy" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='fueleconomy' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_FuelEconomy" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_FuelEconomy">@translator.Translate(userLanguage, "Fuel Economy")</label>
|
<label class="form-check-label stretched-link" for="chkCol_FuelEconomy">@translator.Translate(userLanguage, "Fuel Economy")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Cost" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='unitcost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_UnitCost" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='unitcost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_UnitCost" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
|
<label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Attachment">
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Notes">
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Notes">
|
||||||
<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>
|
||||||
@@ -154,7 +161,7 @@
|
|||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
var elementId = Guid.NewGuid();
|
var elementId = Guid.NewGuid();
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="@elementId">
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="@elementId">
|
||||||
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
@@ -187,6 +194,7 @@
|
|||||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas="fueleconomy" data-unit="@fuelEconomyUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{@translator.Translate(userLanguage, "Fuel Economy")}({fuelEconomyUnit})")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas="fueleconomy" data-unit="@fuelEconomyUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{@translator.Translate(userLanguage, "Fuel Economy")}({fuelEconomyUnit})")</th>
|
||||||
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||||
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Unit Cost")</th>
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Unit Cost")</th>
|
||||||
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
|
||||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
@@ -200,11 +208,12 @@
|
|||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@gasRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditGasRecordModal,@gasRecord.Id)" data-tags='@string.Join(" ", gasRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@gasRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditGasRecordModal,@gasRecord.Id)" data-tags='@string.Join(" ", gasRecord.Tags)'>
|
||||||
<td class="col-2 flex-grow-1 text-truncate" data-column="daterefueled">@gasRecord.Date</td>
|
<td class="col-2 flex-grow-1 text-truncate" data-column="daterefueled">@gasRecord.Date</td>
|
||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@(gasRecord.Mileage == default ? "---" : gasRecord.Mileage.ToString())</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@(gasRecord.Mileage == default ? "---" : gasRecord.Mileage.ToString())</td>
|
||||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="delta">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="delta" data-gas-type="totaldistance">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
|
||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString(gasConsumptionFormat)</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString(gasConsumptionFormat)</td>
|
||||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas-type="fueleconomy" data-aggregated='@(gasRecord.IncludeInAverage.ToString().ToLower())'>@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas-type="fueleconomy" data-aggregated='@(gasRecord.IncludeInAverage.ToString().ToLower())'>@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
||||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
|
||||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
||||||
|
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", gasRecord.Files)</td>
|
||||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</td>
|
||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
@@ -251,6 +260,10 @@
|
|||||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
||||||
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||||
@@ -268,5 +281,12 @@
|
|||||||
{
|
{
|
||||||
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
|
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
|
||||||
}
|
}
|
||||||
|
function getGasModelData(){
|
||||||
|
return {
|
||||||
|
distanceUnit: decodeHTMLEntities('@distanceUnit'),
|
||||||
|
consumptionUnit: decodeHTMLEntities('@consumptionUnit'),
|
||||||
|
gasCostFormat: decodeHTMLEntities('@gasCostFormat'),
|
||||||
|
gasConsumptionFormat: decodeHTMLEntities('@gasConsumptionFormat')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
var useKwh = Model.UseKwh;
|
var useKwh = Model.UseKwh;
|
||||||
var useHours = Model.UseHours;
|
var useHours = Model.UseHours;
|
||||||
var isNew = Model.GasRecord.Id == 0;
|
var isNew = Model.GasRecord.Id == 0;
|
||||||
|
var useUnitFuelCost = userConfig.UseUnitForFuelCost;
|
||||||
string consumptionUnit;
|
string consumptionUnit;
|
||||||
string distanceUnit;
|
string distanceUnit;
|
||||||
if (useKwh)
|
if (useKwh)
|
||||||
@@ -80,8 +81,8 @@
|
|||||||
<input type="text" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
<input type="text" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<select class="form-select form-select-sm" id="gasCostType">
|
<select class="form-select form-select-sm" id="gasCostType">
|
||||||
<option value="total">@translator.Translate(userLanguage,"Total")</option>
|
<!option @(useUnitFuelCost ? "" : "selected") value="total">@translator.Translate(userLanguage,"Total")</!option>
|
||||||
<option value="unit">@translator.Translate(userLanguage,"Unit")</option>
|
<!option @(useUnitFuelCost ? "selected" : "") value="unit">@translator.Translate(userLanguage,"Unit")</!option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,14 +97,7 @@
|
|||||||
<!option value="@tag">@tag</!option>
|
<!option value="@tag">@tag</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@foreach (ExtraField field in Model.GasRecord.ExtraFields)
|
@await Html.PartialAsync("_ExtraField", Model.GasRecord.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="gasRecordNotes">@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="gasRecordNotes">@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>
|
||||||
|
|||||||
@@ -30,14 +30,7 @@
|
|||||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
<label for="gasRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
<label for="gasRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||||
<select multiple class="form-select" id="gasRecordTag"></select>
|
<select multiple class="form-select" id="gasRecordTag"></select>
|
||||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="gasRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="gasRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -28,14 +28,7 @@
|
|||||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="genericRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="genericRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
<label for="genericRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
<label for="genericRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||||
<select multiple class="form-select" id="genericRecordTag"></select>
|
<select multiple class="form-select" id="genericRecordTag"></select>
|
||||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="genericRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
<label for="genericRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
|||||||
@@ -6,10 +6,15 @@
|
|||||||
var barGraphColors = StaticHelper.GetBarChartColors();
|
var barGraphColors = StaticHelper.GetBarChartColors();
|
||||||
var userConfig = config.GetUserConfig(User);
|
var userConfig = config.GetUserConfig(User);
|
||||||
var userLanguage = userConfig.UserLanguage;
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
|
||||||
|
var graphGrace = Decimal.ToInt32(Model.CostData.Max(x => x.Cost) - Model.CostData.Min(x => x.Cost));
|
||||||
|
if (graphGrace < 0 || Model.CostData.Min(x=>x.Cost) - graphGrace < 0)
|
||||||
|
{
|
||||||
|
graphGrace = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@if (Model.CostData.Any(x => x.Cost > 0))
|
@if (Model.CostData.Any(x => x.Cost > 0))
|
||||||
{
|
{
|
||||||
|
|
||||||
<canvas id="bar-chart-mpg"></canvas>
|
<canvas id="bar-chart-mpg"></canvas>
|
||||||
<script>
|
<script>
|
||||||
renderChart();
|
renderChart();
|
||||||
@@ -54,7 +59,8 @@
|
|||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: false,
|
||||||
|
grace: decodeHTMLEntities('@(graphGrace)'),
|
||||||
ticks: {
|
ticks: {
|
||||||
color: useDarkMode ? "#fff" : "#000"
|
color: useDarkMode ? "#fff" : "#000"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,14 +33,14 @@
|
|||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
|
<label for="noteFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="noteFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="noteFiles">
|
||||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
<label for="noteFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="noteFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="noteFiles">
|
||||||
<br />
|
<br />
|
||||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||||
@@ -87,4 +87,4 @@
|
|||||||
function getNoteModelData(){
|
function getNoteModelData(){
|
||||||
return { id: @Model.Id}
|
return { id: @Model.Id}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -109,5 +109,8 @@
|
|||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -54,31 +54,37 @@
|
|||||||
</li>
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Date" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='initialodometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_InitialOdometer" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='initialodometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_InitialOdometer" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_InitialOdometer">@translator.Translate(userLanguage, "Initial Odometer")</label>
|
<label class="form-check-label stretched-link" for="chkCol_InitialOdometer">@translator.Translate(userLanguage, "Initial Odometer")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='distance' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Distance" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='distance' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Distance" checked>
|
||||||
<label class="form-check-label stretched-link" for="chkCol_Distance">@translator.Translate(userLanguage, "Distance")</label>
|
<label class="form-check-label stretched-link" for="chkCol_Distance">@translator.Translate(userLanguage, "Distance")</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Attachment">
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Notes" checked>
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
<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>
|
||||||
@@ -87,7 +93,7 @@
|
|||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
var elementId = Guid.NewGuid();
|
var elementId = Guid.NewGuid();
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="@elementId">
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="@elementId">
|
||||||
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
@@ -118,6 +124,7 @@
|
|||||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@translator.Translate(userLanguage, "Initial Odometer")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@translator.Translate(userLanguage, "Initial Odometer")</th>
|
||||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
|
||||||
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" onclick="toggleSort('odometer-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Distance")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" onclick="toggleSort('odometer-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Distance")</th>
|
||||||
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
|
||||||
<th scope="col" class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
<th scope="col" class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
@@ -131,8 +138,9 @@
|
|||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@odometerRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditOdometerRecordModal,@odometerRecord.Id)" data-tags='@string.Join(" ", odometerRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@odometerRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditOdometerRecordModal,@odometerRecord.Id)" data-tags='@string.Join(" ", odometerRecord.Tags)'>
|
||||||
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date">@odometerRecord.Date.ToShortDateString()</td>
|
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date">@odometerRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@odometerRecord.InitialMileage</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@odometerRecord.InitialMileage</td>
|
||||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@odometerRecord.Mileage</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer" data-record-type="cost">@odometerRecord.Mileage</td>
|
||||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" data-record-type="distance">@(odometerRecord.DistanceTraveled == default ? "---" : odometerRecord.DistanceTraveled)</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" data-record-type="distance">@(odometerRecord.DistanceTraveled == default ? "---" : odometerRecord.DistanceTraveled)</td>
|
||||||
|
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", odometerRecord.Files)</td>
|
||||||
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
|
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
|
||||||
@foreach (string extraFieldColumn in extraFields)
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
{
|
{
|
||||||
@@ -181,6 +189,9 @@
|
|||||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
||||||
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
|
||||||
|
|||||||
@@ -26,14 +26,7 @@
|
|||||||
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
<label for="odometerRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
<label for="odometerRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||||
<select multiple class="form-select" id="odometerRecordTag"></select>
|
<select multiple class="form-select" id="odometerRecordTag"></select>
|
||||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
|
||||||
{
|
|
||||||
var elementId = Guid.NewGuid();
|
|
||||||
<div class="extra-field">
|
|
||||||
<label for="@elementId">@field.Name</label>
|
|
||||||
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="odometerRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<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(use --- to clear all existing notes)")<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>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row swimlane">
|
<div class="row swimlane">
|
||||||
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
|
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||||
<span class="display-7">@translator.Translate(userLanguage,"Planned")</span>
|
<span class="display-7">@translator.Translate(userLanguage,"Planned")</span>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
|
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||||
<span class="display-7">@translator.Translate(userLanguage,"Doing")</span>
|
<span class="display-7">@translator.Translate(userLanguage,"Doing")</span>
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 d-flex flex-column swimlane end" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
|
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||||
<span class="display-7">@translator.Translate(userLanguage,"Done")</span>
|
<span class="display-7">@translator.Translate(userLanguage,"Done")</span>
|
||||||
@@ -127,5 +127,8 @@
|
|||||||
<li><hr class="context-menu-move move-header dropdown-divider"></li>
|
<li><hr class="context-menu-move move-header dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate-vehicle" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate-vehicle" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
|
||||||
|
<li><hr class="context-menu-move move-header dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item context-menu-move move-header context-menu-print-tab-sticker" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
|
||||||
|
<li><hr class="context-menu-move move-header dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item context-menu-move move-header text-danger context-menu-delete" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
<li><a class="dropdown-item context-menu-move move-header text-danger context-menu-delete" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -82,9 +82,9 @@
|
|||||||
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
|
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
|
||||||
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
|
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
|
||||||
</select>
|
</select>
|
||||||
<label for="reminderRecurringMonth">@translator.Translate(userLanguage, "Month")</label>
|
<label for="reminderRecurringMonth">@translator.Translate(userLanguage, "Time")</label>
|
||||||
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Date || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
|
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Date || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
|
||||||
<!option value="Other" @(Model.ReminderMonthInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.ReminderMonthInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
|
<!option value="Other" @(Model.ReminderMonthInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.ReminderMonthInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval} {Model.CustomMonthIntervalUnit}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
|
||||||
<!option value="OneMonth" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage, "1 Month")</!option>
|
<!option value="OneMonth" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage, "1 Month")</!option>
|
||||||
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage,"3 Months")</!option>
|
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage,"3 Months")</!option>
|
||||||
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage,"6 Months")</!option>
|
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage,"6 Months")</!option>
|
||||||
@@ -136,6 +136,7 @@
|
|||||||
<script>
|
<script>
|
||||||
var customMileageInterval = @Model.CustomMileageInterval;
|
var customMileageInterval = @Model.CustomMileageInterval;
|
||||||
var customMonthInterval = @Model.CustomMonthInterval;
|
var customMonthInterval = @Model.CustomMonthInterval;
|
||||||
|
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
|
||||||
function getReminderRecordModelData() {
|
function getReminderRecordModelData() {
|
||||||
return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')}
|
return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user