Compare commits

..

53 Commits

Author SHA1 Message Date
Hargata Softworks
bd7cbffe10 Merge pull request #741 from hargata/Hargata/60
SimplyAuto Import
2024-11-25 09:27:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b293882f75 add more column aliases. 2024-11-25 09:26:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0984b2049b Added support for SimplyAuto Fuel Import. 2024-11-25 09:18:10 -07:00
Hargata Softworks
d4ec4989e7 Merge pull request #740 from hargata/Hargata/739
Refactored code in ConfigHelper.
2024-11-25 08:42:28 -07:00
DESKTOP-T0O5CDB\DESK-555BD
230433f784 Refactored a few more methods that rely on the IConfiguration object to use IConfigHelper instead. 2024-11-25 08:40:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d4dda481be pattern. 2024-11-25 08:18:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1d3b94f544 Refactored code in ConfigHelper. 2024-11-25 08:14:56 -07:00
Hargata Softworks
78434f0016 Merge pull request #737 from hargata/Hargata/overhaul.reminder.tab
Reminder Record Tab overhaul.
2024-11-24 20:42:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c75145de49 Reminder Record Tab overhaul. 2024-11-24 20:19:48 -07:00
Hargata Softworks
5e3480f9f3 Merge pull request #736 from hargata/Hargata/context.menu.remaster
Remaster the context menu
2024-11-24 14:41:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f74fdc8ed4 context menu remaster for both notes and reminders. 2024-11-24 14:40:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9ba62f8bf8 prevent multiple gas record edits if selected ids is less than 2 2024-11-24 13:48:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2b9dea2b6 Added context menu to plan records. 2024-11-24 13:47:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f92aa22375 Remaster the context menu 2024-11-23 22:22:16 -07:00
Hargata Softworks
d65001f412 Merge pull request #735 from hargata/Hargata/costtable.row.highlight
added row highlighting when viewing cost tables.
2024-11-23 15:34:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9ea223cd0a added row highlighting when viewing cost tables. 2024-11-23 15:34:07 -07:00
Hargata Softworks
addf73bc27 Merge pull request #734 from hargata/Hargata/fix.aggregate.label
Fuel Consumption label now updates based on fuel consumption unit sel…
2024-11-23 11:32:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3f853d0d5e Fuel Consumption label now updates based on fuel consumption unit selected. 2024-11-23 11:31:48 -07:00
Hargata Softworks
4c46278be1 Merge pull request #733 from hargata/Hargata/select.mode
Added select mode.
2024-11-22 14:06:53 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0edc51083c Added select mode. 2024-11-22 14:04:36 -07:00
Hargata Softworks
d906bec333 Merge pull request #732 from hargata/Hargata/fix.demo
more minor changes.
2024-11-22 10:57:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a47c4942b7 more minor changes. 2024-11-22 10:56:53 -07:00
Hargata Softworks
cd8df9e938 Merge pull request #731 from hargata/Hargata/fix.demo
Update the demo db.
2024-11-22 10:53:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5c37f98274 Update the demo db. 2024-11-22 10:51:08 -07:00
Hargata Softworks
f452436dbd Merge pull request #730 from hargata/Hargata/523
Sort notes in ascending order by description
2024-11-22 10:27:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9375bb103b spacing. 2024-11-22 10:25:40 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3222579cb4 allow notes to be sorted in ascending order as well as minor code clean up. 2024-11-22 10:20:53 -07:00
Hargata Softworks
4348b50e54 Merge pull request #728 from hargata/Hargata/716
additional enhancements
2024-11-21 23:15:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3b7c58b6e8 additional enhancements 2024-11-21 23:15:05 -07:00
Hargata Softworks
664b89a5df Merge pull request #727 from hargata/Hargata/716
Added functionality to display multi year trends.
2024-11-21 22:14:45 -07:00
DESKTOP-T0O5CDB\DESK-555BD
628c22ad61 Added functionality to display multi year trends. 2024-11-21 22:13:08 -07:00
Hargata Softworks
db62a041c4 Merge pull request #726 from hargata/Hargata/454.2
Fixed wording.
2024-11-21 15:07:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ea3ffea797 Fixed wording. 2024-11-21 15:07:24 -07:00
Hargata Softworks
c49c8d67a2 Merge pull request #725 from hargata/Hargata/454
Allow users to add supplies onto existing records.
2024-11-21 13:56:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3b01df4c56 Updated verbiage 2024-11-21 13:51:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9221516bad fix bug when there are multiple supplies with the same id. 2024-11-20 21:18:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1988cf54f5 Allow users to add supplies onto existing records. 2024-11-20 20:48:38 -07:00
Hargata Softworks
8a8d8979a7 Merge pull request #724 from hargata/Hargata/429
Replenish Supplies By Deleting Records
2024-11-20 12:14:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e983b39f8e Fixed minor bug with plan type not displaying correctly in kiosk mode. 2024-11-20 12:13:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b6c5cd4b46 important bug fix. 2024-11-20 11:57:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
210b27ab25 Allow users to to restore supplies by either deleting the record or deleting the requistion history. 2024-11-20 11:54:29 -07:00
Hargata Softworks
63e9e5ecec Merge pull request #720 from hargata/Hargata/planner.drag.drop.fix
1.4.1 Changes
2024-11-19 14:45:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
771ca15ea2 bump version. 2024-11-19 13:42:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b6b403b5e2 styling fixes. 2024-11-19 13:41:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9976c94481 Merge branch 'main' into Hargata/planner.drag.drop.fix 2024-11-19 13:31:29 -07:00
Hargata Softworks
b9dca8ceae Merge pull request #722 from hargata/Hargata/operationresponse.cleanup.2
Added Conditional OperationalResponse constructor.
2024-11-19 13:14:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
03b9331a9a Added Conditional OperationalResponse constructor. 2024-11-19 13:04:40 -07:00
Hargata Softworks
f629834d6a Merge pull request #721 from hargata/Hargata/operationresponse.cleanup.1
Clean up OperationResponse method Part 1
2024-11-19 12:37:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
25952cce50 Clean up OperationResponse method Part 1 2024-11-19 12:26:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6eba83ccec added enhancement to bulk gas edit modal 2024-11-19 10:46:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3c1ce80528 Added user setting for fixing gas consumption to two decimal places. 2024-11-19 10:43:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7851e87b2 Save Vehicle Specific Parameters in SessionStorage. 2024-11-19 09:11:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d8e06fd968 Fix an issue with the drag and drop functionality on the planner page. 2024-11-17 11:02:11 -07:00
88 changed files with 1593 additions and 661 deletions

View File

@@ -150,9 +150,7 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
@@ -165,23 +163,18 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/servicerecords/add")]
public IActionResult AddServiceRecord(int vehicleId, GenericRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
@@ -209,16 +202,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Service Record via API - Description: {serviceRecord.Description}");
response.Success = true;
response.Message = "Service Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Service Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -228,9 +217,7 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
@@ -243,23 +230,18 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/repairrecords/add")]
public IActionResult AddRepairRecord(int vehicleId, GenericRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
@@ -287,16 +269,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Repair Record via API - Description: {repairRecord.Description}");
response.Success = true;
response.Message = "Repair Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Repair Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -306,9 +284,7 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
@@ -321,23 +297,18 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/upgraderecords/add")]
public IActionResult AddUpgradeRecord(int vehicleId, GenericRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
@@ -365,16 +336,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Upgrade Record via API - Description: {upgradeRecord.Description}");
response.Success = true;
response.Message = "Upgrade Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Upgrade Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -384,9 +351,7 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
@@ -398,22 +363,17 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/taxrecords/add")]
public IActionResult AddTaxRecord(int vehicleId, TaxRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, and Cost cannot be empty."));
}
try
{
@@ -429,16 +389,12 @@ namespace CarCareTracker.Controllers
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
response.Success = true;
response.Message = "Tax Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Tax Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -448,9 +404,7 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
@@ -464,9 +418,7 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
@@ -484,21 +436,16 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/odometerrecords/add")]
public IActionResult AddOdometerRecord(int vehicleId, OdometerRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Odometer))
{
response.Success = false;
response.Message = "Input object invalid, Date and Odometer cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date and Odometer cannot be empty."));
}
try
{
@@ -514,15 +461,11 @@ namespace CarCareTracker.Controllers
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
response.Success = true;
response.Message = "Odometer Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Odometer Record Added"));
} catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -532,9 +475,7 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
@@ -558,13 +499,10 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/gasrecords/add")]
public IActionResult AddGasRecord(int vehicleId, GasRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
@@ -574,10 +512,8 @@ namespace CarCareTracker.Controllers
string.IsNullOrWhiteSpace(input.MissedFuelUp)
)
{
response.Success = false;
response.Message = "Input object invalid, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty."));
}
try
{
@@ -607,16 +543,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Gas record via API - Mileage: {gasRecord.Mileage.ToString()}");
response.Success = true;
response.Message = "Gas Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Gas Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -626,11 +558,8 @@ namespace CarCareTracker.Controllers
{
if (vehicleId == default)
{
var response = new OperationResponse();
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
@@ -678,17 +607,17 @@ namespace CarCareTracker.Controllers
}
if (!operationResponses.Any())
{
return Json(new OperationResponse { Success = false, Message = "No Emails Sent, No Vehicles Available or No Recipients Configured" });
return Json(OperationResponse.Failed("No Emails Sent, No Vehicles Available or No Recipients Configured"));
}
else if (operationResponses.All(x => x.Success))
{
return Json(new OperationResponse { Success = true, Message = $"Emails Sent({operationResponses.Count()})" });
return Json(OperationResponse.Succeed($"Emails Sent({operationResponses.Count()})"));
} else if (operationResponses.All(x => !x.Success))
{
return Json(new OperationResponse { Success = false, Message = $"All Emails Failed({operationResponses.Count()}), Check SMTP Settings" });
return Json(OperationResponse.Failed($"All Emails Failed({operationResponses.Count()}), Check SMTP Settings"));
} else
{
return Json(new OperationResponse { Success = true, Message = $"Emails Sent({operationResponses.Count(x => x.Success)}), Emails Failed({operationResponses.Count(x => !x.Success)}), Check Recipient Settings" });
return Json(OperationResponse.Succeed($"Emails Sent({operationResponses.Count(x => x.Success)}), Emails Failed({operationResponses.Count(x => !x.Success)}), Check Recipient Settings"));
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]

View File

@@ -55,7 +55,7 @@ namespace CarCareTracker.Controllers
}
}
}
var successResponse = new OperationResponse { Success = true, Message = "Token Generated!" };
var successResponse = OperationResponse.Succeed("Token Generated!");
return Json(successResponse);
} else
{

View File

@@ -32,7 +32,7 @@ namespace CarCareTracker.Controllers
var originalFileName = Path.GetFileNameWithoutExtension(file.FileName);
if (originalFileName == "en_US")
{
return Json(new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." });
return Json(OperationResponse.Failed("The translation file name en_US is reserved."));
}
var fileName = UploadFile(file);
//move file from temp to translation folder.
@@ -41,9 +41,9 @@ namespace CarCareTracker.Controllers
if (!string.IsNullOrWhiteSpace(uploadedFilePath))
{
var result = _fileHelper.RenameFile(uploadedFilePath, originalFileName);
return Json(new OperationResponse { Success = result, Message = string.Empty });
return Json(OperationResponse.Conditional(result));
}
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
[HttpPost]

View File

@@ -280,12 +280,12 @@ namespace CarCareTracker.Controllers
var result = _loginLogic.UpdateUserDetails(userId, userAccount);
return Json(result);
}
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
[HttpGet]
@@ -493,16 +493,16 @@ namespace CarCareTracker.Controllers
}
if (translationsDownloaded > 0)
{
return Json(new OperationResponse() { Success = true, Message = $"{translationsDownloaded} Translations Downloaded" });
return Json(OperationResponse.Succeed($"{translationsDownloaded} Translations Downloaded"));
} else
{
return Json(new OperationResponse() { Success = false, Message = "No Translations Downloaded" });
return Json(OperationResponse.Failed("No Translations Downloaded"));
}
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
return Json(new OperationResponse() { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
public ActionResult GetVehicleSelector(int vehicleId)

View File

@@ -15,7 +15,7 @@ namespace CarCareTracker.Controllers
private IConfigHelper _configHelper;
private IFileHelper _fileHelper;
private readonly ILogger<MigrationController> _logger;
public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, IConfiguration serverConfig, ILogger<MigrationController> logger)
public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, ILogger<MigrationController> logger)
{
_configHelper = configHelper;
_fileHelper = fileHelper;
@@ -66,7 +66,7 @@ namespace CarCareTracker.Controllers
{
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
{
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
return Json(OperationResponse.Failed("Postgres connection not set up"));
}
var tempFolder = $"temp/{Guid.NewGuid()}";
var tempPath = $"{tempFolder}/cartracker.db";
@@ -419,24 +419,24 @@ namespace CarCareTracker.Controllers
#endregion
var destFilePath = $"{fullFolderPath}.zip";
ZipFile.CreateFromDirectory(fullFolderPath, destFilePath);
return Json(new OperationResponse { Success = true, Message = $"/{tempFolder}.zip" });
return Json(OperationResponse.Succeed($"/{tempFolder}.zip"));
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
public IActionResult Import(string fileName)
{
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
{
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
return Json(OperationResponse.Failed("Postgres connection not set up"));
}
var fullFileName = _fileHelper.GetFullFilePath(fileName);
if (string.IsNullOrWhiteSpace(fullFileName))
{
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
try
{
@@ -744,12 +744,12 @@ namespace CarCareTracker.Controllers
}
}
#endregion
return Json(new OperationResponse { Success = true, Message = "Data Imported Successfully" });
return Json(OperationResponse.Succeed("Data Imported Successfully"));
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
}

View File

@@ -91,10 +91,21 @@ namespace CarCareTracker.Controllers
};
return PartialView("_GasModal", viewModel);
}
private bool DeleteGasRecordWithChecks(int gasRecordId)
{
var existingRecord = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteGasRecordById(int gasRecordId)
{
var result = _gasRecordDataAccess.DeleteGasRecordById(gasRecordId);
var result = DeleteGasRecordWithChecks(gasRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");

View File

@@ -273,13 +273,22 @@ namespace CarCareTracker.Controllers
var requiredExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)mode).ExtraFields.Where(x => x.IsRequired).Select(y => y.Name);
foreach (ImportModel importModel in records)
{
var parsedDate = DateTime.Now.Date;
if (!string.IsNullOrWhiteSpace(importModel.Date))
{
parsedDate = DateTime.Parse(importModel.Date);
}
else if (!string.IsNullOrWhiteSpace(importModel.Day) && !string.IsNullOrWhiteSpace(importModel.Month) && !string.IsNullOrWhiteSpace(importModel.Year))
{
parsedDate = new DateTime(int.Parse(importModel.Year), int.Parse(importModel.Month), int.Parse(importModel.Day));
}
if (mode == ImportMode.GasRecord)
{
//convert to gas model.
var convertedRecord = new GasRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
@@ -335,9 +344,9 @@ namespace CarCareTracker.Controllers
var convertedRecord = new ServiceRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {importModel.Date}" : importModel.Description,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
@@ -360,7 +369,7 @@ namespace CarCareTracker.Controllers
var convertedRecord = new OdometerRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Date = parsedDate,
InitialMileage = string.IsNullOrWhiteSpace(importModel.InitialOdometer) ? 0 : decimal.ToInt32(decimal.Parse(importModel.InitialOdometer, NumberStyles.Any)),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
@@ -394,9 +403,9 @@ namespace CarCareTracker.Controllers
var convertedRecord = new CollisionRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {importModel.Date}" : importModel.Description,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
@@ -419,9 +428,9 @@ namespace CarCareTracker.Controllers
var convertedRecord = new UpgradeRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {importModel.Date}" : importModel.Description,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
@@ -444,7 +453,7 @@ namespace CarCareTracker.Controllers
var convertedRecord = new SupplyRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Date = parsedDate,
PartNumber = importModel.PartNumber,
PartSupplier = importModel.PartSupplier,
Quantity = decimal.Parse(importModel.PartQuantity, NumberStyles.Any),
@@ -461,8 +470,8 @@ namespace CarCareTracker.Controllers
var convertedRecord = new TaxRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {importModel.Date}" : importModel.Description,
Date = parsedDate,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),

View File

@@ -12,7 +12,7 @@ namespace CarCareTracker.Controllers
public IActionResult GetNotesByVehicleId(int vehicleId)
{
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
result = result.OrderByDescending(x => x.Pinned).ToList();
result = result.OrderByDescending(x => x.Pinned).ThenBy(x => x.Description).ToList();
return PartialView("_Notes", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -45,10 +45,21 @@ namespace CarCareTracker.Controllers
var result = _noteDataAccess.GetNoteById(noteId);
return PartialView("_NoteModal", result);
}
private bool DeleteNoteWithChecks(int noteId)
{
var existingRecord = _noteDataAccess.GetNoteById(noteId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _noteDataAccess.DeleteNoteById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteNoteById(int noteId)
{
var result = _noteDataAccess.DeleteNoteById(noteId);
var result = DeleteNoteWithChecks(noteId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");

View File

@@ -140,10 +140,21 @@ namespace CarCareTracker.Controllers
};
return PartialView("_OdometerRecordModal", convertedResult);
}
private bool DeleteOdometerRecordWithChecks(int odometerRecordId)
{
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
{
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(odometerRecordId);
var result = DeleteOdometerRecordWithChecks(odometerRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");

View File

@@ -27,12 +27,16 @@ namespace CarCareTracker.Controllers
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (planRecord.Supplies.Any())
{
planRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description);
planRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description));
if (planRecord.CopySuppliesAttachment)
{
planRecord.Files.AddRange(GetSuppliesAttachments(planRecord.Supplies));
}
}
if (planRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
if (result)
{
@@ -47,11 +51,11 @@ namespace CarCareTracker.Controllers
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
if (planRecord.Id == default && existingRecord)
{
return Json(new OperationResponse { Success = false, Message = "A template with that description already exists for this vehicle" });
return Json(OperationResponse.Failed("A template with that description already exists for this vehicle"));
}
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _planRecordTemplateDataAccess.SavePlanRecordTemplateToVehicle(planRecord);
return Json(new OperationResponse { Success = result, Message = result ? "Template Added" : StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Conditional(result, "Template Added", string.Empty));
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
@@ -72,7 +76,7 @@ namespace CarCareTracker.Controllers
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(new OperationResponse { Success = false, Message = "Unable to find template" });
return Json(OperationResponse.Failed("Unable to find template"));
}
if (existingRecord.Supplies.Any())
{
@@ -81,7 +85,7 @@ namespace CarCareTracker.Controllers
}
else
{
return Json(new OperationResponse { Success = false, Message = "Template has No Supplies" });
return Json(OperationResponse.Failed("Template has No Supplies"));
}
}
[HttpPost]
@@ -90,7 +94,7 @@ namespace CarCareTracker.Controllers
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(new OperationResponse { Success = false, Message = "Unable to find template" });
return Json(OperationResponse.Failed("Unable to find template"));
}
if (existingRecord.Supplies.Any())
{
@@ -98,11 +102,11 @@ namespace CarCareTracker.Controllers
var supplyAvailability = CheckSupplyRecordsAvailability(existingRecord.Supplies);
if (supplyAvailability.Any(x => x.Missing))
{
return Json(new OperationResponse { Success = false, Message = "Missing Supplies, Please Delete This Template and Recreate It." });
return Json(OperationResponse.Failed("Missing Supplies, Please Delete This Template and Recreate It."));
}
else if (supplyAvailability.Any(x => x.Insufficient))
{
return Json(new OperationResponse { Success = false, Message = "Insufficient Supplies" });
return Json(OperationResponse.Failed("Insufficient Supplies"));
}
}
if (existingRecord.ReminderRecordId != default)
@@ -111,7 +115,7 @@ namespace CarCareTracker.Controllers
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(existingRecord.ReminderRecordId);
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
{
return Json(new OperationResponse { Success = false, Message = "Missing or Non-recurring Reminder, Please Delete This Template and Recreate It." });
return Json(OperationResponse.Failed("Missing or Non-recurring Reminder, Please Delete This Template and Recreate It."));
}
}
//populate createdDate
@@ -127,7 +131,7 @@ namespace CarCareTracker.Controllers
}
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord.ToPlanRecord());
return Json(new OperationResponse { Success = result, Message = result ? "Plan Record Added" : StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Conditional(result, "Plan Record Added", string.Empty));
}
[HttpGet]
public IActionResult GetAddPlanRecordPartialView()
@@ -147,6 +151,10 @@ namespace CarCareTracker.Controllers
[HttpPost]
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0)
{
if (planRecordId == default)
{
return Json(false);
}
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
existingRecord.Progress = planProgress;
existingRecord.DateModified = DateTime.Now;
@@ -254,7 +262,18 @@ namespace CarCareTracker.Controllers
[HttpPost]
public IActionResult DeletePlanRecordById(int planRecordId)
{
var result = _planRecordDataAccess.DeletePlanRecordById(planRecordId);
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return Json(false);
}
//restore any requisitioned supplies if it has not been converted to other record types.
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}");

View File

@@ -149,10 +149,21 @@ namespace CarCareTracker.Controllers
};
return PartialView("_ReminderRecordModal", convertedResult);
}
private bool DeleteReminderRecordWithChecks(int reminderRecordId)
{
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteReminderRecordById(int reminderRecordId)
{
var result = _reminderRecordDataAccess.DeleteReminderRecordById(reminderRecordId);
var result = DeleteReminderRecordWithChecks(reminderRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");

View File

@@ -40,12 +40,16 @@ namespace CarCareTracker.Controllers
collisionRecord.Files = collisionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (collisionRecord.Supplies.Any())
{
collisionRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description);
collisionRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description));
if (collisionRecord.CopySuppliesAttachment)
{
collisionRecord.Files.AddRange(GetSuppliesAttachments(collisionRecord.Supplies));
}
}
if (collisionRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
}
//push back any reminders
if (collisionRecord.ReminderRecordId.Any())
{
@@ -87,10 +91,26 @@ namespace CarCareTracker.Controllers
};
return PartialView("_CollisionRecordModal", convertedResult);
}
private bool DeleteCollisionRecordWithChecks(int collisionRecordId)
{
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
{
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(collisionRecordId);
var result = DeleteCollisionRecordWithChecks(collisionRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");

View File

@@ -311,13 +311,13 @@ namespace CarCareTracker.Controllers
var result = _fileHelper.MakeAttachmentsExport(attachmentData);
if (string.IsNullOrWhiteSpace(result))
{
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
return Json(new OperationResponse { Success = true, Message = result });
return Json(OperationResponse.Succeed(result));
}
else
{
return Json(new OperationResponse { Success = false, Message = "No Attachments Found" });
return Json(OperationResponse.Failed("No Attachments Found"));
}
}
public IActionResult GetReportParameters()
@@ -558,6 +558,55 @@ namespace CarCareTracker.Controllers
}).ToList();
return PartialView("_GasCostByMonthReport", groupedRecord);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetCostByMonthAndYearByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
{
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.RepairRecord))
{
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetRepairRecordSum(repairRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.UpgradeRecord))
{
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.GasRecord))
{
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.TaxRecord))
{
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
{
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year, true));
}
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId, x.Year }).OrderByDescending(x=>x.Key.Year).Select(x => new CostForVehicleByMonth
{
Year = x.Key.Year,
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Max(y => y.DistanceTraveled),
MonthId = x.Key.MonthId
}).ToList();
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var userConfig = _config.GetUserConfig(User);
var viewModel = new CostDistanceTableForVehicle { CostData = groupedRecord };
viewModel.DistanceUnit = vehicleData.UseHours ? "h" : userConfig.UseMPG ? "mi." : "km";
return PartialView("_CostDistanceTableReport", viewModel);
}
[HttpGet]
public IActionResult GetAdditionalWidgets()
{

View File

@@ -40,12 +40,16 @@ namespace CarCareTracker.Controllers
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (serviceRecord.Supplies.Any())
{
serviceRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description);
serviceRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description));
if (serviceRecord.CopySuppliesAttachment)
{
serviceRecord.Files.AddRange(GetSuppliesAttachments(serviceRecord.Supplies));
}
}
if (serviceRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
}
//push back any reminders
if (serviceRecord.ReminderRecordId.Any())
{
@@ -87,10 +91,26 @@ namespace CarCareTracker.Controllers
};
return PartialView("_ServiceRecordModal", convertedResult);
}
private bool DeleteServiceRecordWithChecks(int serviceRecordId)
{
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteServiceRecordById(int serviceRecordId)
{
var result = _serviceRecordDataAccess.DeleteServiceRecordById(serviceRecordId);
var result = DeleteServiceRecordWithChecks(serviceRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");

View File

@@ -58,6 +58,7 @@ namespace CarCareTracker.Controllers
//create new requisitionrrecord
var requisitionRecord = new SupplyUsageHistory
{
Id = supply.SupplyId,
Date = dateRequisitioned,
Description = usageDescription,
Quantity = supply.Quantity,
@@ -72,6 +73,44 @@ namespace CarCareTracker.Controllers
}
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))]
[HttpGet]
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
@@ -161,6 +200,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetSupplyRecordForEditById(int supplyRecordId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
if (result.RequisitionHistory.Any())
{
//requisition history when viewed through the supply is always immutable.
result.RequisitionHistory = result.RequisitionHistory.Select(x => new SupplyUsageHistory { Id = default, Cost = x.Cost, Description = x.Description, Date = x.Date, PartNumber = x.PartNumber, Quantity = x.Quantity }).ToList();
}
//convert to Input object.
var convertedResult = new SupplyRecordInput
{
@@ -180,10 +224,24 @@ namespace CarCareTracker.Controllers
};
return PartialView("_SupplyRecordModal", convertedResult);
}
private bool DeleteSupplyRecordWithChecks(int supplyRecordId)
{
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
if (existingRecord.VehicleId != default)
{
//security check only if not editing shop supply.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
}
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
{
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(supplyRecordId);
var result = DeleteSupplyRecordWithChecks(supplyRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");

View File

@@ -110,10 +110,21 @@ namespace CarCareTracker.Controllers
};
return PartialView("_TaxRecordModal", convertedResult);
}
private bool DeleteTaxRecordWithChecks(int taxRecordId)
{
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteTaxRecordById(int taxRecordId)
{
var result = _taxRecordDataAccess.DeleteTaxRecordById(taxRecordId);
var result = DeleteTaxRecordWithChecks(taxRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");

View File

@@ -40,12 +40,16 @@ namespace CarCareTracker.Controllers
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (upgradeRecord.Supplies.Any())
{
upgradeRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description);
upgradeRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description));
if (upgradeRecord.CopySuppliesAttachment)
{
upgradeRecord.Files.AddRange(GetSuppliesAttachments(upgradeRecord.Supplies));
}
}
if (upgradeRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
}
//push back any reminders
if (upgradeRecord.ReminderRecordId.Any())
{
@@ -87,10 +91,26 @@ namespace CarCareTracker.Controllers
};
return PartialView("_UpgradeRecordModal", convertedResult);
}
private bool DeleteUpgradeRecordWithChecks(int upgradeRecordId)
{
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
{
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(upgradeRecordId);
var result = DeleteUpgradeRecordWithChecks(upgradeRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");

View File

@@ -188,15 +188,15 @@ namespace CarCareTracker.Controllers
}
else
{
return Json(new OperationResponse { Success = false, Message = "Both vehicles already have identical collaborators" });
return Json(OperationResponse.Failed("Both vehicles already have identical collaborators"));
}
}
return Json(new OperationResponse { Success = true, Message = "Collaborators Copied" });
return Json(OperationResponse.Succeed("Collaborators Copied"));
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
@@ -401,31 +401,31 @@ namespace CarCareTracker.Controllers
switch (importMode)
{
case ImportMode.ServiceRecord:
result = _serviceRecordDataAccess.DeleteServiceRecordById(recordId);
result = DeleteServiceRecordWithChecks(recordId);
break;
case ImportMode.RepairRecord:
result = _collisionRecordDataAccess.DeleteCollisionRecordById(recordId);
result = DeleteCollisionRecordWithChecks(recordId);
break;
case ImportMode.UpgradeRecord:
result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(recordId);
result = DeleteUpgradeRecordWithChecks(recordId);
break;
case ImportMode.GasRecord:
result = _gasRecordDataAccess.DeleteGasRecordById(recordId);
result = DeleteGasRecordWithChecks(recordId);
break;
case ImportMode.TaxRecord:
result = _taxRecordDataAccess.DeleteTaxRecordById(recordId);
result = DeleteTaxRecordWithChecks(recordId);
break;
case ImportMode.SupplyRecord:
result = _supplyRecordDataAccess.DeleteSupplyRecordById(recordId);
result = DeleteSupplyRecordWithChecks(recordId);
break;
case ImportMode.NoteRecord:
result = _noteDataAccess.DeleteNoteById(recordId);
result = DeleteNoteWithChecks(recordId);
break;
case ImportMode.OdometerRecord:
result = _odometerRecordDataAccess.DeleteOdometerRecordById(recordId);
result = DeleteOdometerRecordWithChecks(recordId);
break;
case ImportMode.ReminderRecord:
result = _reminderRecordDataAccess.DeleteReminderRecordById(recordId);
result = DeleteReminderRecordWithChecks(recordId);
break;
}
}
@@ -506,6 +506,7 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
existingRecord.Id = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
}
break;
@@ -513,6 +514,7 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
existingRecord.Id = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(existingRecord);
}
break;
@@ -520,6 +522,7 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
existingRecord.Id = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(existingRecord);
}
break;
@@ -541,6 +544,7 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
existingRecord.Id = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(existingRecord);
}
break;
@@ -565,6 +569,15 @@ namespace CarCareTracker.Controllers
result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingRecord);
}
break;
case ImportMode.PlanRecord:
{
var existingRecord = _planRecordDataAccess.GetPlanRecordById(recordId);
existingRecord.Id = default;
existingRecord.ReminderRecordId = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
}
break;
}
}
if (result)
@@ -589,7 +602,8 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
existingRecord.Id = default;
foreach(int vehicleId in vehicleIds)
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
foreach (int vehicleId in vehicleIds)
{
existingRecord.VehicleId = vehicleId;
result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
@@ -600,6 +614,7 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
existingRecord.Id = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
foreach (int vehicleId in vehicleIds)
{
existingRecord.VehicleId = vehicleId;
@@ -611,6 +626,7 @@ namespace CarCareTracker.Controllers
{
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
existingRecord.Id = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
foreach (int vehicleId in vehicleIds)
{
existingRecord.VehicleId = vehicleId;
@@ -684,6 +700,19 @@ namespace CarCareTracker.Controllers
}
}
break;
case ImportMode.PlanRecord:
{
var existingRecord = _planRecordDataAccess.GetPlanRecordById(recordId);
existingRecord.Id = default;
existingRecord.ReminderRecordId = default;
existingRecord.RequisitionHistory = new List<SupplyUsageHistory>();
foreach (int vehicleId in vehicleIds)
{
existingRecord.VehicleId = vehicleId;
result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
}
}
break;
}
}
if (result)

View File

@@ -10,6 +10,7 @@ namespace CarCareTracker.Helper
{
OpenIDConfig GetOpenIDConfig();
ReminderUrgencyConfig GetReminderUrgencyConfig();
MailConfig GetMailConfig();
UserConfig GetUserConfig(ClaimsPrincipal user);
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
bool AuthenticateRootUser(string username, string password);
@@ -29,35 +30,30 @@ namespace CarCareTracker.Helper
{
private readonly IConfiguration _config;
private readonly IUserConfigDataAccess _userConfig;
private readonly ILogger<IConfigHelper> _logger;
private IMemoryCache _cache;
public ConfigHelper(IConfiguration serverConfig,
IUserConfigDataAccess userConfig,
IMemoryCache memoryCache)
IMemoryCache memoryCache,
ILogger<IConfigHelper> logger)
{
_config = serverConfig;
_userConfig = userConfig;
_cache = memoryCache;
_logger = logger;
}
public string GetWebHookUrl()
{
var webhook = _config["LUBELOGGER_WEBHOOK"];
if (string.IsNullOrWhiteSpace(webhook))
{
webhook = "";
}
var webhook = CheckString("LUBELOGGER_WEBHOOK");
return webhook;
}
public bool GetCustomWidgetsEnabled()
{
return bool.Parse(_config["LUBELOGGER_CUSTOM_WIDGETS"] ?? "false");
return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS"));
}
public string GetMOTD()
{
var motd = _config["LUBELOGGER_MOTD"];
if (string.IsNullOrWhiteSpace(motd))
{
motd = "";
}
var motd = CheckString("LUBELOGGER_MOTD");
return motd;
}
public OpenIDConfig GetOpenIDConfig()
@@ -70,27 +66,25 @@ namespace CarCareTracker.Helper
ReminderUrgencyConfig reminderUrgencyConfig = _config.GetSection("ReminderUrgencyConfig").Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig();
return reminderUrgencyConfig;
}
public MailConfig GetMailConfig()
{
MailConfig mailConfig = _config.GetSection("MailConfig").Get<MailConfig>() ?? new MailConfig();
return mailConfig;
}
public string GetLogoUrl()
{
var logoUrl = _config["LUBELOGGER_LOGO_URL"];
if (string.IsNullOrWhiteSpace(logoUrl))
{
logoUrl = "/defaults/lubelogger_logo.png";
}
var logoUrl = CheckString("LUBELOGGER_LOGO_URL", "/defaults/lubelogger_logo.png");
return logoUrl;
}
public string GetAllowedFileUploadExtensions()
{
var allowedFileExtensions = _config["LUBELOGGER_ALLOWED_FILE_EXTENSIONS"];
if (string.IsNullOrWhiteSpace(allowedFileExtensions)){
return StaticHelper.DefaultAllowedFileExtensions;
}
var allowedFileExtensions = CheckString("LUBELOGGER_ALLOWED_FILE_EXTENSIONS", StaticHelper.DefaultAllowedFileExtensions);
return allowedFileExtensions;
}
public bool AuthenticateRootUser(string username, string password)
{
var rootUsername = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
var rootPassword = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
var rootUsername = CheckString(nameof(UserConfig.UserNameHash));
var rootPassword = CheckString(nameof(UserConfig.UserPasswordHash));
if (string.IsNullOrWhiteSpace(rootUsername) || string.IsNullOrWhiteSpace(rootPassword))
{
return false;
@@ -99,8 +93,8 @@ namespace CarCareTracker.Helper
}
public bool AuthenticateRootUserOIDC(string email)
{
var rootEmail = _config[nameof(UserConfig.DefaultReminderEmail)] ?? string.Empty;
var rootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]);
var rootEmail = CheckString(nameof(UserConfig.DefaultReminderEmail));
var rootUserOIDC = CheckBool(CheckString(nameof(UserConfig.EnableRootUserOIDC)));
if (!rootUserOIDC || string.IsNullOrWhiteSpace(rootEmail))
{
return false;
@@ -109,27 +103,22 @@ namespace CarCareTracker.Helper
}
public string GetServerLanguage()
{
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
var serverLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US");
return serverLanguage;
}
public bool GetServerDisabledRegistration()
{
var registrationDisabled = bool.Parse(_config[nameof(UserConfig.DisableRegistration)]);
var registrationDisabled = CheckBool(CheckString(nameof(UserConfig.DisableRegistration)));
return registrationDisabled;
}
public string GetServerPostgresConnection()
{
if (!string.IsNullOrWhiteSpace(_config["POSTGRES_CONNECTION"]))
{
return _config["POSTGRES_CONNECTION"];
} else
{
return string.Empty;
}
var postgresConnection = CheckString("POSTGRES_CONNECTION");
return postgresConnection;
}
public bool GetServerEnableShopSupplies()
{
return bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)] ?? "false");
return CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies)));
}
public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData)
{
@@ -159,6 +148,7 @@ namespace CarCareTracker.Helper
}
catch (Exception ex)
{
_logger.LogWarning(ex.Message);
return false;
}
} else
@@ -179,37 +169,72 @@ namespace CarCareTracker.Helper
var result = _userConfig.DeleteUserConfig(userId);
return result;
}
private bool CheckBool(string value, bool defaultValue = false)
{
try
{
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
else if (bool.TryParse(value, out bool result))
{
return result;
}
else
{
return defaultValue;
}
} catch (Exception ex)
{
_logger.LogWarning($"ConfigHelper Warning: You might be missing keys in appsettings.json, Message: ${ex.Message}");
return defaultValue;
}
}
private string CheckString(string configName, string defaultValue = "")
{
try
{
var configValue = _config[configName] ?? defaultValue;
return configValue;
} catch(Exception ex)
{
_logger.LogWarning($"ConfigHelper Warning: You might be missing keys in appsettings.json, Message: ${ex.Message}");
return defaultValue;
}
}
public UserConfig GetUserConfig(ClaimsPrincipal user)
{
var serverConfig = new UserConfig
{
EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]),
UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]),
UseSystemColorMode = bool.Parse(_config[nameof(UserConfig.UseSystemColorMode)]),
UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]),
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
EnableRootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]),
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
AutomaticDecimalFormat = bool.Parse(_config[nameof(UserConfig.AutomaticDecimalFormat)]),
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
EnableAutoReminderRefresh = bool.Parse(_config[nameof(UserConfig.EnableAutoReminderRefresh)]),
EnableAutoOdometerInsert = bool.Parse(_config[nameof(UserConfig.EnableAutoOdometerInsert)]),
PreferredGasMileageUnit = _config[nameof(UserConfig.PreferredGasMileageUnit)],
PreferredGasUnit = _config[nameof(UserConfig.PreferredGasUnit)],
UserLanguage = _config[nameof(UserConfig.UserLanguage)],
HideSoldVehicles = bool.Parse(_config[nameof(UserConfig.HideSoldVehicles)]),
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(),
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>(),
EnableCsvImports = CheckBool(CheckString(nameof(UserConfig.EnableCsvImports)), true),
UseDarkMode = CheckBool(CheckString(nameof(UserConfig.UseDarkMode))),
UseSystemColorMode = CheckBool(CheckString(nameof(UserConfig.UseSystemColorMode))),
UseMPG = CheckBool(CheckString(nameof(UserConfig.UseMPG)), true),
UseDescending = CheckBool(CheckString(nameof(UserConfig.UseDescending))),
EnableAuth = CheckBool(CheckString(nameof(UserConfig.EnableAuth))),
EnableRootUserOIDC = CheckBool(CheckString(nameof(UserConfig.EnableRootUserOIDC))),
HideZero = CheckBool(CheckString(nameof(UserConfig.HideZero))),
AutomaticDecimalFormat = CheckBool(CheckString(nameof(UserConfig.AutomaticDecimalFormat))),
UseUKMPG = CheckBool(CheckString(nameof(UserConfig.UseUKMPG))),
UseMarkDownOnSavedNotes = CheckBool(CheckString(nameof(UserConfig.UseMarkDownOnSavedNotes))),
UseThreeDecimalGasCost = CheckBool(CheckString(nameof(UserConfig.UseThreeDecimalGasCost)), true),
UseThreeDecimalGasConsumption = CheckBool(CheckString(nameof(UserConfig.UseThreeDecimalGasConsumption)), true),
EnableAutoReminderRefresh = CheckBool(CheckString(nameof(UserConfig.EnableAutoReminderRefresh))),
EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))),
PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)),
PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)),
UserLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US"),
HideSoldVehicles = CheckBool(CheckString(nameof(UserConfig.HideSoldVehicles))),
EnableShopSupplies = CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies))),
EnableExtraFieldColumns = CheckBool(CheckString(nameof(UserConfig.EnableExtraFieldColumns))),
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>() ?? new UserConfig().VisibleTabs,
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>() ?? new UserConfig().TabOrder,
UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(),
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)]),
DefaultReminderEmail = _config[nameof(UserConfig.DefaultReminderEmail)],
DisableRegistration = bool.Parse(_config[nameof(UserConfig.DisableRegistration)])
DefaultTab = (ImportMode)int.Parse(CheckString(nameof(UserConfig.DefaultTab), "8")),
DefaultReminderEmail = CheckString(nameof(UserConfig.DefaultReminderEmail)),
DisableRegistration = CheckBool(CheckString(nameof(UserConfig.DisableRegistration)))
};
int userId = 0;
if (user != null)

View File

@@ -18,12 +18,12 @@ namespace CarCareTracker.Helper
private readonly IFileHelper _fileHelper;
private readonly ILogger<MailHelper> _logger;
public MailHelper(
IConfiguration config,
IConfigHelper config,
IFileHelper fileHelper,
ILogger<MailHelper> logger
) {
//load mailConfig from Configuration
mailConfig = config.GetSection("MailConfig").Get<MailConfig>() ?? new MailConfig();
mailConfig = config.GetMailConfig();
_fileHelper = fileHelper;
_logger = logger;
}
@@ -31,79 +31,79 @@ namespace CarCareTracker.Helper
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "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}";
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
return OperationResponse.Succeed("Email Sent!");
} else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public OperationResponse NotifyUserForPasswordReset(string emailAddress, string token)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
{
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "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}";
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
return OperationResponse.Succeed("Email Sent!");
}
else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
{
return new OperationResponse { Success = false, Message = "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 emailBody = $"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);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
return OperationResponse.Succeed("Email Sent!");
}
else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (!emailAddresses.Any())
{
return new OperationResponse { Success = false, Message = "No recipients could be found" };
return OperationResponse.Failed("No recipients could be found");
}
if (!reminders.Any())
{
return new OperationResponse { Success = false, Message = "No reminders could be found" };
return OperationResponse.Failed("No reminders could be found");
}
//get email template, this file has to exist since it's a static file.
var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate);
@@ -123,14 +123,14 @@ namespace CarCareTracker.Helper
var result = SendEmail(emailAddresses, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
return OperationResponse.Succeed("Email Sent!");
} else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
} catch (Exception ex)
{
return new OperationResponse { Success = false, Message = ex.Message };
return OperationResponse.Failed(ex.Message);
}
}
private bool SendEmail(List<string> emailTo, string emailSubject, string emailBody) {

View File

@@ -79,6 +79,7 @@ namespace CarCareTracker.Helper
Description = reminder.Description,
Notes = reminder.Notes,
Metric = reminder.Metric,
UserMetric = reminder.Metric,
IsRecurring = reminder.IsRecurring,
Tags = reminder.Tags
};

View File

@@ -5,93 +5,166 @@ namespace CarCareTracker.Helper
{
public interface IReportHelper
{
IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0, bool sortIntoYear = false);
}
public class ReportHelper: IReportHelper
{
public IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
odometerRecords.RemoveAll(x => x.Date.Year != year);
}
return odometerRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = 0,
DistanceTraveled = x.Sum(y=>y.DistanceTraveled)
});
return odometerRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = 0,
DistanceTraveled = x.Sum(y => y.DistanceTraveled)
});
} else
{
return odometerRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = 0,
DistanceTraveled = x.Sum(y => y.DistanceTraveled)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
serviceRecords.RemoveAll(x => x.Date.Year != year);
}
return serviceRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return serviceRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return serviceRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
repairRecords.RemoveAll(x => x.Date.Year != year);
}
return repairRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return repairRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return repairRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
upgradeRecords.RemoveAll(x => x.Date.Year != year);
}
return upgradeRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return upgradeRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return upgradeRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
gasRecords.RemoveAll(x => x.Date.Year != year);
}
return gasRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return gasRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return gasRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
taxRecords.RemoveAll(x => x.Date.Year != year);
}
return taxRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return taxRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return taxRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
}
}

View File

@@ -9,16 +9,16 @@ namespace CarCareTracker.Helper
/// </summary>
public static class StaticHelper
{
public static string VersionNumber = "1.4.0";
public static string DbName = "data/cartracker.db";
public static string UserConfigPath = "config/userConfig.json";
public static string AdditionalWidgetsPath = "data/widgets.html";
public static string GenericErrorMessage = "An error occurred, please try again later";
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
public static string TranslationPath = "https://hargata.github.io/lubelog_translations";
public static string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
public const string VersionNumber = "1.4.1";
public const string DbName = "data/cartracker.db";
public const string UserConfigPath = "config/userConfig.json";
public const string AdditionalWidgetsPath = "data/widgets.html";
public const string GenericErrorMessage = "An error occurred, please try again later";
public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
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 TranslationPath = "https://hargata.github.io/lubelog_translations";
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 static string GetTitleCaseReminderUrgency(ReminderUrgency input)
{
@@ -601,6 +601,27 @@ namespace CarCareTracker.Helper
_csv.NextRecord();
}
}
public static string HideZeroCost(string input, bool hideZero, string decorations = "")
{
if (input == 0M.ToString("C2") && hideZero)
{
return "---";
} else
{
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
}
}
public static string HideZeroCost(decimal input, bool hideZero, string decorations = "")
{
if (input == default && hideZero)
{
return "---";
}
else
{
return string.IsNullOrWhiteSpace(decorations) ? input.ToString("C2") : $"{input.ToString("C2")}{decorations}";
}
}
public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();

View File

@@ -130,15 +130,15 @@ namespace CarCareTracker.Helper
bool isDefaultLanguage = userLanguage == "en_US";
if (isDefaultLanguage && !create)
{
return new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." };
return OperationResponse.Failed("The translation file name en_US is reserved.");
}
if (string.IsNullOrWhiteSpace(userLanguage))
{
return new OperationResponse { Success = false, Message = "File name is not provided." };
return OperationResponse.Failed("File name is not provided.");
}
if (!translations.Any())
{
return new OperationResponse { Success = false, Message = "Translation has no data." };
return OperationResponse.Failed("Translation has no data.");
}
var translationFilePath = isDefaultLanguage ? _fileHelper.GetFullFilePath($"/defaults/en_US.json") : _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
try
@@ -159,12 +159,12 @@ namespace CarCareTracker.Helper
//write to file
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(translations));
}
return new OperationResponse { Success = true, Message = "Translation Updated" };
return OperationResponse.Succeed("Translation Updated");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public string ExportTranslation(Dictionary<string, string> translations)

View File

@@ -71,13 +71,13 @@ namespace CarCareTracker.Logic
var existingUser = _userData.GetUserRecordById(userId);
if (existingUser.Id == default)
{
return new OperationResponse { Success = false, Message = "Invalid user" };
return OperationResponse.Failed("Invalid user");
}
//validate user token
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != existingUser.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
if (!string.IsNullOrWhiteSpace(credentials.UserName) && existingUser.UserName != credentials.UserName)
{
@@ -85,7 +85,7 @@ namespace CarCareTracker.Logic
var existingUserWithUserName = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUserWithUserName.Id != default)
{
return new OperationResponse { Success = false, Message = "Username already taken" };
return OperationResponse.Failed("Username already taken");
}
existingUser.UserName = credentials.UserName;
}
@@ -95,7 +95,7 @@ namespace CarCareTracker.Logic
var existingUserWithEmailAddress = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUserWithEmailAddress.Id != default)
{
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
return OperationResponse.Failed("A user with that email already exists");
}
existingUser.EmailAddress = credentials.EmailAddress;
}
@@ -107,7 +107,7 @@ namespace CarCareTracker.Logic
//delete token
_tokenData.DeleteToken(existingToken.Id);
var result = _userData.SaveUserRecord(existingUser);
return new OperationResponse { Success = result, Message = result ? "User Updated" : StaticHelper.GenericErrorMessage };
return OperationResponse.Conditional(result, "User Updated", string.Empty);
}
public OperationResponse RegisterOpenIdUser(LoginModel credentials)
{
@@ -115,21 +115,21 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName))
{
return new OperationResponse { Success = false, Message = "Username cannot be blank" };
return OperationResponse.Failed("Username cannot be blank");
}
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUser.Id != default)
{
return new OperationResponse { Success = false, Message = "Username already taken" };
return OperationResponse.Failed("Username already taken");
}
var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUserWithEmail.Id != default)
{
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
return OperationResponse.Failed("A user with that email already exists");
}
_tokenData.DeleteToken(existingToken.Id);
var newUser = new UserData()
@@ -141,11 +141,11 @@ namespace CarCareTracker.Logic
var result = _userData.SaveUserRecord(newUser);
if (result)
{
return new OperationResponse { Success = true, Message = "You will be logged in briefly." };
return OperationResponse.Succeed("You will be logged in briefly.");
}
else
{
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
return OperationResponse.Failed("Something went wrong, please try again later.");
}
}
//handles user registration
@@ -155,22 +155,22 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
//token is valid, check if username and password is acceptable and that username is unique.
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName) || string.IsNullOrWhiteSpace(credentials.Password))
{
return new OperationResponse { Success = false, Message = "Neither username nor password can be blank" };
return OperationResponse.Failed("Neither username nor password can be blank");
}
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUser.Id != default)
{
return new OperationResponse { Success = false, Message = "Username already taken" };
return OperationResponse.Failed("Username already taken");
}
var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUserWithEmail.Id != default)
{
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
return OperationResponse.Failed("A user with that email already exists");
}
//username is unique then we delete the token and create the user.
_tokenData.DeleteToken(existingToken.Id);
@@ -183,11 +183,11 @@ namespace CarCareTracker.Logic
var result = _userData.SaveUserRecord(newUser);
if (result)
{
return new OperationResponse { Success = true, Message = "You will be redirected to the login page briefly." };
return OperationResponse.Succeed("You will be redirected to the login page briefly.");
}
else
{
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
return OperationResponse.Failed();
}
}
/// <summary>
@@ -205,24 +205,24 @@ namespace CarCareTracker.Logic
}
//for security purposes we want to always return true for this method.
//otherwise someone can spam the reset password method to sniff out users.
return new OperationResponse { Success = true, Message = "If your user exists in the system you should receive an email shortly with instructions on how to proceed." };
return OperationResponse.Succeed("If your user exists in the system you should receive an email shortly with instructions on how to proceed.");
}
public OperationResponse ResetPasswordByUser(LoginModel credentials)
{
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
if (string.IsNullOrWhiteSpace(credentials.Password))
{
return new OperationResponse { Success = false, Message = "New Password cannot be blank" };
return OperationResponse.Failed("New Password cannot be blank");
}
//if token is valid.
var existingUser = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUser.Id == default)
{
return new OperationResponse { Success = false, Message = "Unable to locate user" };
return OperationResponse.Failed("Unable to locate user");
}
existingUser.Password = GetHash(credentials.Password);
var result = _userData.SaveUserRecord(existingUser);
@@ -230,10 +230,10 @@ namespace CarCareTracker.Logic
_tokenData.DeleteToken(existingToken.Id);
if (result)
{
return new OperationResponse { Success = true, Message = "Password resetted, you will be redirected to login page shortly." };
return OperationResponse.Succeed("Password resetted, you will be redirected to login page shortly.");
} else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
/// <summary>
@@ -310,7 +310,7 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
if (existingToken.Id != default)
{
return new OperationResponse { Success = false, Message = "There is an existing token tied to this email address" };
return OperationResponse.Failed("There is an existing token tied to this email address");
}
var token = new Token()
{
@@ -323,16 +323,16 @@ namespace CarCareTracker.Logic
result = _mailHelper.NotifyUserForRegistration(emailAddress, token.Body).Success;
if (!result)
{
return new OperationResponse { Success = false, Message = "Token Generated, but Email failed to send, please check your SMTP settings." };
return OperationResponse.Failed("Token Generated, but Email failed to send, please check your SMTP settings.");
}
}
if (result)
{
return new OperationResponse { Success = true, Message = "Token Generated!" };
return OperationResponse.Succeed("Token Generated!");
}
else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public bool DeleteUserToken(int tokenId)
@@ -351,18 +351,18 @@ namespace CarCareTracker.Logic
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUser.Id == default)
{
return new OperationResponse { Success = false, Message = "Unable to find user" };
return OperationResponse.Failed("Unable to find user");
}
var newPassword = Guid.NewGuid().ToString().Substring(0, 8);
existingUser.Password = GetHash(newPassword);
var result = _userData.SaveUserRecord(existingUser);
if (result)
{
return new OperationResponse { Success = true, Message = newPassword };
return OperationResponse.Succeed(newPassword);
}
else
{
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
return OperationResponse.Failed();
}
}
#endregion

View File

@@ -51,16 +51,16 @@ namespace CarCareTracker.Logic
var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(existingUser.Id, vehicleId);
if (userAccess != null)
{
return new OperationResponse { Success = false, Message = "User is already a collaborator" };
return OperationResponse.Failed("User is already a collaborator");
}
var result = AddUserAccessToVehicle(existingUser.Id, vehicleId);
if (result)
{
return new OperationResponse { Success = true, Message = "Collaborator Added" };
return OperationResponse.Succeed("Collaborator Added");
}
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
return new OperationResponse { Success = false, Message = $"Unable to find user {username} in the system" };
return OperationResponse.Failed($"Unable to find user {username} in the system");
}
public bool DeleteCollaboratorFromVehicle(int userId, int vehicleId)
{

View File

@@ -311,7 +311,7 @@ namespace CarCareTracker.Logic
}
if (vehiclePlans.Any())
{
var convertedPlans = vehiclePlans.Select(x => new PlanRecord { Priority = x.Priority, Progress = x.Progress, Notes = x.Notes, RequisitionHistory = x.RequisitionHistory, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" });
var convertedPlans = vehiclePlans.Select(x => new PlanRecord { ImportMode = x.ImportMode, Priority = x.Priority, Progress = x.Progress, Notes = x.Notes, RequisitionHistory = x.RequisitionHistory, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" });
plans.AddRange(convertedPlans);
}
}

View File

@@ -8,18 +8,21 @@ namespace CarCareTracker.MapProfile
public ImportMapper()
{
Map(m => m.Date).Name(["date", "fuelup_date"]);
Map(m => m.Day).Name(["day"]);
Map(m => m.Month).Name(["month"]);
Map(m => m.Year).Name(["year"]);
Map(m => m.DateCreated).Name(["datecreated"]);
Map(m => m.DateModified).Name(["datemodified"]);
Map(m => m.InitialOdometer).Name(["initialodometer"]);
Map(m => m.Odometer).Name(["odometer"]);
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
Map(m => m.Odometer).Name(["odometer", "odo"]);
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed", "qty"]);
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
Map(m => m.Notes).Name("notes", "note");
Map(m => m.Price).Name(["price"]);
Map(m => m.PartialFuelUp).Name(["partial_fuelup"]);
Map(m => m.PartialFuelUp).Name(["partial_fuelup", "partial tank", "partial_fill"]);
Map(m => m.IsFillToFull).Name(["isfilltofull", "filled up"]);
Map(m => m.Description).Name(["description"]);
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup"]);
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup", "missed fill up", "missed_fill"]);
Map(m => m.PartSupplier).Name(["partsupplier"]);
Map(m => m.PartQuantity).Name(["partquantity"]);
Map(m => m.PartNumber).Name(["partnumber"]);

View File

@@ -28,7 +28,7 @@ namespace CarCareTracker.Middleware
_httpContext = httpContext;
_dataProtector = securityProvider.CreateProtector("login");
_loginLogic = loginLogic;
enableAuth = bool.Parse(configuration["EnableAuth"]);
enableAuth = bool.Parse(configuration["EnableAuth"] ?? "false");
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{

View File

@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public CollisionRecord ToCollisionRecord() { return new CollisionRecord {
Id = Id,

View File

@@ -1,8 +1,33 @@
namespace CarCareTracker.Models
using CarCareTracker.Helper;
namespace CarCareTracker.Models
{
public class OperationResponse
public class OperationResponseBase
{
public bool Success { get; set; }
public string Message { get; set; }
}
public class OperationResponse: OperationResponseBase
{
public static OperationResponse Succeed(string message = "")
{
return new OperationResponse { Success = true, Message = message };
}
public static OperationResponse Failed(string message = "")
{
if (string.IsNullOrWhiteSpace(message))
{
message = StaticHelper.GenericErrorMessage;
}
return new OperationResponse { Success = false, Message = message};
}
public static OperationResponse Conditional(bool result, string successMessage = "", string errorMessage = "")
{
if (string.IsNullOrWhiteSpace(errorMessage))
{
errorMessage = StaticHelper.GenericErrorMessage;
}
return new OperationResponse { Success = result, Message = result ? successMessage : errorMessage };
}
}
}

View File

@@ -17,6 +17,7 @@
public decimal Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public PlanRecord ToPlanRecord() { return new PlanRecord {
Id = Id,

View File

@@ -9,6 +9,10 @@
public string Description { get; set; }
public string Notes { get; set; }
/// <summary>
/// The metric the user selected to calculate the urgency of this reminder.
/// </summary>
public ReminderMetric UserMetric { get; set; } = ReminderMetric.Date;
/// <summary>
/// Reason why this reminder is urgent
/// </summary>
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class CostDistanceTableForVehicle
{
public string DistanceUnit { get; set; } = "mi.";
public List<CostForVehicleByMonth> CostData { get; set; } = new List<CostForVehicleByMonth>();
}
}

View File

@@ -2,9 +2,11 @@
{
public class CostForVehicleByMonth
{
public int Year { get; set; }
public int MonthId { get; set; }
public string MonthName { get; set; }
public decimal Cost { get; set; }
public int DistanceTraveled { get; set; }
public decimal CostPerDistanceTraveled { get { if (DistanceTraveled > 0) { return Cost / DistanceTraveled; } else { return 0M; } } }
}
}

View File

@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public ServiceRecord ToServiceRecord() { return new ServiceRecord {
Id = Id,

View File

@@ -6,6 +6,9 @@
public class ImportModel
{
public string Date { get; set; }
public string Day { get; set; }
public string Month { get; set; }
public string Year { get; set; }
public string DateCreated { get; set; }
public string DateModified { get; set; }
public string Type { get; set; }

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class SupplyRequisitionHistory
{
public string CostInputId { get; set; }
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
}
}

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class SupplyStore
{
public string Tab { get; set; }
public bool AdditionalSupplies { get; set; }
}
}

View File

@@ -1,6 +1,7 @@
namespace CarCareTracker.Models
{
public class SupplyUsageHistory {
public int Id { get; set; }
public DateTime Date { get; set; }
public string PartNumber { get; set; }
public string Description { get; set; }

View File

@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord {
Id = Id,

View File

@@ -13,6 +13,7 @@
public bool HideZero { get; set; }
public bool UseUKMPG {get;set;}
public bool UseThreeDecimalGasCost { get; set; }
public bool UseThreeDecimalGasConsumption { get; set; }
public bool UseMarkDownOnSavedNotes { get; set; }
public bool EnableAutoReminderRefresh { get; set; }
public bool EnableAutoOdometerInsert { get; set; }

View File

@@ -65,8 +65,8 @@ builder.Services.AddSingleton<IFileHelper, FileHelper>();
builder.Services.AddSingleton<IGasHelper, GasHelper>();
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
//configure logic

View File

@@ -2,12 +2,12 @@
@{
ViewData["Title"] = "Admin Panel";
}
@inject IConfiguration config;
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
bool emailServerIsSetup = true;
var mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
var mailConfig = config.GetMailConfig();
var userLanguage = config.GetServerLanguage();
if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
emailServerIsSetup = false;

View File

@@ -37,18 +37,24 @@
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UserConfig.UseDescending">
<label class="form-check-label" for="useDescending">@translator.Translate(userLanguage, "Sort lists in Descending Order(Newest to Oldest)")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.UserConfig.HideZero">
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 Costs with ---")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="automaticDecimalFormat" checked="@Model.UserConfig.AutomaticDecimalFormat">
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.UserConfig.HideZero">
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 Costs with ---")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="automaticDecimalFormat" checked="@Model.UserConfig.AutomaticDecimalFormat">
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
</div>
</div>
<div class="form-check form-switch">
<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">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption">
<label class="form-check-label" for="useThreeDecimalGasConsumption">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Consumption")</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMarkDownOnSavedNotes" checked="@Model.UserConfig.UseMarkDownOnSavedNotes">
<label class="form-check-label" for="useMarkDownOnSavedNotes">@translator.Translate(userLanguage, "Display Saved Notes in Markdown")</label>
@@ -65,13 +71,15 @@
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableExtraFieldColumns" checked="@Model.UserConfig.EnableExtraFieldColumns">
<label class="form-check-label" for="enableExtraFieldColumns">@translator.Translate(userLanguage, "Show Extra Field Columns")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Enabling this may cause performance issues")</small></label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideSoldVehicles" checked="@Model.UserConfig.HideSoldVehicles">
<label class="form-check-label" for="hideSoldVehicles">@translator.Translate(userLanguage, "Hide Sold Vehicles")</label>
</div>
<div class="form-check form-switch @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
<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>
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideSoldVehicles" checked="@Model.UserConfig.HideSoldVehicles">
<label class="form-check-label" for="hideSoldVehicles">@translator.Translate(userLanguage, "Hide Sold Vehicles")</label>
</div>
<div class="form-check form-switch form-check-inline @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
<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>
</div>
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{

View File

@@ -1,10 +1,10 @@
@{
ViewData["Title"] = "Database Migration";
}
@inject IConfiguration config;
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
var userLanguage = config.GetServerLanguage();
}
@using CarCareTracker.Helper
@model AdminViewModel

View File

@@ -42,6 +42,7 @@
function deleteCollaborator(userId, vehicleId) {
$.post('/Vehicle/DeleteCollaboratorFromVehicle', {userId: userId, vehicleId: vehicleId}, function(data){
if (data) {
successToast('Collaborator Removed');
refreshCollaborators();
} else {
errorToast(genericErrorMessage());
@@ -68,6 +69,7 @@
var vehicleId = GetVehicleId().vehicleId;
$.post('/Vehicle/AddCollaboratorsToVehicle', { username: result.value.userName, vehicleId: vehicleId }, function (data) {
if (data.success) {
successToast(data.message);
refreshCollaborators();
} else {
errorToast(data.message)

View File

@@ -44,10 +44,7 @@
}
<label for="collisionRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
@if (isNew)
{
@await Html.PartialAsync("_SupplyStore", "RepairRecord")
}
@await Html.PartialAsync("_SupplyStore", new SupplyStore { Tab = "RepairRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="collisionRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="collisionRecordTag">
@foreach (string tag in Model.Tags)
@@ -126,7 +123,7 @@
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Repair Record")</button>
}
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
@await Html.PartialAsync("_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "collisionRecordCost" })
<script>
var uploadedFiles = [];
var selectedSupplies = [];

View File

@@ -46,6 +46,13 @@
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('accident-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
@@ -125,7 +132,7 @@
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(collisionRecord.Date)">@collisionRecord.Date.ToShortDateString()</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-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</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-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -145,12 +152,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -164,21 +173,21 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'ServiceRecord')">@translator.Translate(userLanguage, "Service Records")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'UpgradeRecord')">@translator.Translate(userLanguage, "Upgrades")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Service Records")</span><i class="bi bi-card-checklist"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Upgrades")</span><i class="bi bi-wrench-adjustable"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Delete")</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 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><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</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>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'RepairRecord')"><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>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -0,0 +1,94 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model CostDistanceTableForVehicle
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var hideZero = userConfig.HideZero;
var years = Model.CostData.Select(x => x.Year).OrderByDescending(x => x).Distinct();
var months = Model.CostData.OrderBy(x => x.MonthId).Select(x => x.MonthName).Distinct();
}
@if (Model.CostData.Any())
{
<div>
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "Vehicle Monthly Cost Breakdown"))</h5>
<button type="button" class="btn-close" onclick="hideDataTable()" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12" style="overflow-x:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" style="cursor:pointer;" onclick="toggleBarChartTableData()" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@(translator.Translate(userLanguage, "Year"))</th>
@foreach(int year in years){
if (year != default){
<th scope="col" style="cursor:pointer;" onclick="toggleBarChartTableData()" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@year</th>
}
}
</tr>
</thead>
<tbody>
@foreach(string month in months){
<tr class="d-flex">
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@month</td>
@foreach(int year in years){
if (year != default){
{
var dataToDisplay = Model.CostData.Where(x => x.Year == year && x.MonthName == month).FirstOrDefault();
if (dataToDisplay != null && dataToDisplay != default)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(dataToDisplay.Cost.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(dataToDisplay.DistanceTraveled != default ? $"{dataToDisplay.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(dataToDisplay.CostPerDistanceTraveled.ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
} else {
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@("---")</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>
}
</tbody>
<tfoot>
<tr class="d-flex fw-bold">
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@(translator.Translate(userLanguage, "Total"))</td>
@foreach (int year in years)
{
if (year != default)
{
{
var yearDataToDisplay = Model.CostData.Where(x => x.Year == year);
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>
<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>
<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>
}
else
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@("---")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero)}")</td>
}
}
}
}
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
}
else
{
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.")</h4>
</div>
}

View File

@@ -16,28 +16,33 @@
new Chart($("#pie-chart"), {
type: 'pie',
data: {
labels: [decodeHTMLEntities('@translator.Translate(userLanguage, "Service Records")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Repairs")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Upgrades")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Tax")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel")')],
labels: [
decodeHTMLEntities('@translator.Translate(userLanguage, "Service Records")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Repairs")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Upgrades")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Tax")')
],
datasets: [
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Type")'),
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ffa600", "#ff6361" ],
data: [
globalParseFloat('@Model.ServiceRecordSum'),
globalParseFloat('@Model.CollisionRecordSum'),
globalParseFloat('@Model.UpgradeRecordSum'),
globalParseFloat('@Model.TaxRecordSum'),
globalParseFloat('@Model.GasRecordSum')
globalParseFloat('@Model.GasRecordSum'),
globalParseFloat('@Model.TaxRecordSum')
]
}
]
},
options: {
onClick: (e) => {
showDataTable();
onClick: (e, a, c) => {
showDataTable(a);
},
onHover: (e, a, c) => {
a.length > 0 ? c.canvas.style.cursor = 'pointer' : c.canvas.style.cursor = 'default';
},
plugins: {
legend: {

View File

@@ -27,43 +27,45 @@
</tr>
</thead>
<tbody>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Service Records")</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.ServiceRecordPerDay == default ? "---" : Model.ServiceRecordPerDay.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.ServiceRecordPerMile == default ? "---" :Model.ServiceRecordPerMile.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.ServiceRecordSum == default ? "---" :Model.ServiceRecordSum.ToString("C2"))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Service Records")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Repairs")</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.CollisionRecordPerDay == default ? "---" :Model.CollisionRecordPerDay.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.CollisionRecordPerMile == default ? "---" :Model.CollisionRecordPerMile.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.CollisionRecordSum == default ? "---" :Model.CollisionRecordSum.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Upgrades")</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.UpgradeRecordPerDay == default ? "---" :Model.UpgradeRecordPerDay.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.UpgradeRecordPerMile == default ? "---" :Model.UpgradeRecordPerMile.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.UpgradeRecordSum == default ? "---" :Model.UpgradeRecordSum.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Fuel")</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.GasRecordPerDay == default ? "---" :Model.GasRecordPerDay.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.GasRecordPerMile == default ? "---" :Model.GasRecordPerMile.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.GasRecordSum == default ? "---" :Model.GasRecordSum.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Taxes")</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.TaxRecordPerDay == default ? "---" :Model.TaxRecordPerDay.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.TaxRecordPerMile == default ? "---" : Model.TaxRecordPerMile.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.TaxRecordSum == default ? "---" :Model.TaxRecordSum.ToString("C2"))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.TotalPerDay == default ? "---" : Model.TotalPerDay.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.TotalPerMile == default ? "---" : Model.TotalPerMile.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(hideZero && Model.TotalCost == default ? "---" : Model.TotalCost.ToString("C2"))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordSum.ToString("C2"), hideZero))</td>
</tr>
</tbody>
<tfoot>
<tr class="d-flex fw-bold">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalCost.ToString("C2"), hideZero))</td>
</tr>
</tfoot>
</table>
</div>
</div>

View File

@@ -10,7 +10,9 @@
var useUKMPG = userConfig.UseUKMPG;
var hideZero = userConfig.HideZero;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
var gasCostFormat = useThreeDecimals ? "C3" : "C2";
var gasConsumptionFormat = useThreeDecimalsConsumption ? "F3" : "F2";
var userLanguage = userConfig.UserLanguage;
var useKwh = Model.UseKwh;
var useHours = Model.UseHours;
@@ -66,7 +68,7 @@
<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">@($"{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>
@foreach (string recordTag in recordTags)
{
@@ -93,6 +95,13 @@
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchGasTableRows()">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
@@ -192,7 +201,7 @@
<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-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="delta">@(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("F")</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-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>
@@ -215,12 +224,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -234,15 +245,15 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Delete")</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 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><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</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>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -52,6 +52,12 @@
]
},
options: {
onClick: (e, a, c) => {
showBarChartTable(a);
},
onHover: (e, a, c) => {
a.length > 0 ? c.canvas.style.cursor = 'pointer' : c.canvas.style.cursor = 'default';
},
plugins: {
title: {
display: true,

View File

@@ -7,6 +7,8 @@
var useMPG = userConfig.UseMPG;
var useUKMPG = userConfig.UseUKMPG;
var userLanguage = userConfig.UserLanguage;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
var useKwh = Model.UseKwh;
var useHours = Model.UseHours;
var isNew = Model.GasRecord.Id == 0;
@@ -62,7 +64,7 @@
}
</div>
<label for="gasRecordGallons">@($"{translator.Translate(userLanguage, "Fuel Consumption")}({consumptionUnit})")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimalsConsumption ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
<label class="form-check-label" for="gasIsFillToFull">@translator.Translate(userLanguage,"Is Filled To Full")</label>
@@ -75,7 +77,7 @@
@if (isNew)
{
<div class="input-group">
<input type="text" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" 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">
<select class="form-select form-select-sm" id="gasCostType">
<option value="total">@translator.Translate(userLanguage,"Total")</option>
@@ -85,7 +87,7 @@
</div>
} else
{
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
<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,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
}
<label for="gasRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="gasRecordTag">

View File

@@ -5,6 +5,8 @@
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
}
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage,"Edit Multiple Gas Records")</h5>
@@ -23,9 +25,9 @@
<label for="gasRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<input type="number" inputmode="numeric" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="gasRecordConsumption">@translator.Translate(userLanguage, "Fuel Consumption")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" id="gasRecordConsumption" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimalsConsumption ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordConsumption" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="gasRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" 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>
<select multiple class="form-select" id="gasRecordTag"></select>
@foreach (ExtraField field in Model.EditRecord.ExtraFields)

View File

@@ -6,6 +6,7 @@
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var enableCsvImports = userConfig.EnableCsvImports;
}
<div class="row">
<div class="d-flex justify-content-between">
@@ -23,7 +24,30 @@
</datalist>
</div>
<div>
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Note")</button>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Note")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('notes-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
</ul>
</div>
}
else
{
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Note")</button>
}
</div>
</div>
</div>
@@ -37,8 +61,8 @@
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-3">@translator.Translate(userLanguage,"Description")</th>
<th scope="col" class="col-9">@translator.Translate(userLanguage,"Note")</th>
<th scope="col" class="col-4 col-md-3 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-8 col-md-9 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Note")</th>
</tr>
</thead>
<tbody>
@@ -47,20 +71,22 @@
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@note.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditNoteModal,@note.Id)" data-tags='@string.Join(" ", note.Tags)'>
@if (note.Pinned)
{
<td class="col-3"><i class='bi bi-pin-fill me-2'></i>@note.Description</td>
<td class="col-4 col-md-3 flex-grow-1 flex-shrink-1 text-truncate"><i class='bi bi-pin-fill me-2'></i>@note.Description</td>
} else
{
<td class="col-3">@note.Description</td>
<td class="col-4 col-md-3 flex-grow-1 flex-shrink-1 text-truncate">@note.Description</td>
}
<td class="col-9 text-truncate" data-record-type="cost">@StaticHelper.TruncateStrings(note.NoteText, 100)</td>
<td class="col-8 col-md-9 flex-grow-1 flex-shrink-1 text-truncate" data-record-type="cost">@StaticHelper.TruncateStrings(note.NoteText, 100)</td>
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -74,14 +100,14 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, true)">@translator.Translate(userLanguage, "Toggle Pin")</a></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, false, true)">@translator.Translate(userLanguage, "Pin")</a></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, false, false)">@translator.Translate(userLanguage, "Unpin")</a></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, true)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Toggle Pin")</span><i class="bi bi-pin-angle-fill"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, false, true)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Pin")</span><i class="bi bi-pin-angle-fill"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, false, false)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Unpin")</span><i class="bi bi-pin-angle"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'NoteRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'NoteRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'NoteRecord')">@translator.Translate(userLanguage, "Delete")</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 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>

View File

@@ -46,6 +46,13 @@
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('odometer-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
@@ -145,12 +152,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -164,17 +173,17 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleOdometerRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleOdometerRecords(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple context-menu-deselect-all dropdown-divider"></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="recalculateDistance()">@translator.Translate(userLanguage, "Recalculate Distance")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="recalculateDistance()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Recalculate Distance")</span><i class="bi bi-plus-slash-minus"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Delete")</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 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><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</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>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -1,5 +1,5 @@
@model PlanRecord
<div class="taskCard @(Model.Progress == PlanProgress.Done ? "nodrag" : "") text-dark user-select-none mt-2 mb-2" draggable="@(Model.Progress == PlanProgress.Done ? "false" : "true")" ondragstart="dragStart(event, @Model.Id)" onclick="@(Model.Progress == PlanProgress.Done ? $"deletePlanRecord({Model.Id}, true)" : $"showEditPlanRecordModal({Model.Id})")">
<div class="taskCard @(Model.Progress == PlanProgress.Done ? "nodrag" : "") text-dark user-select-none mt-2 mb-2" draggable="@(Model.Progress == PlanProgress.Done ? "false" : "true")" ondragstart="dragStart(event, @Model.Id)" onclick="@(Model.Progress == PlanProgress.Done ? $"deletePlanRecord({Model.Id}, true)" : $"showEditPlanRecordModal({Model.Id})")" oncontextmenu="@($"showPlanTableContextMenu(this, {Model.Id}, '{Model.Progress}')")" onmouseup="stopEvent()" ontouchstart="detectPlanItemLongTouch(this, @Model.Id, '@Model.Progress')" ontouchend="detectPlanItemTouchEndPremature(this)">
<div class="card-body">
<div class="row">
<div class="col-12 col-lg-8 text-truncate">

View File

@@ -21,10 +21,7 @@
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
@if (isNew)
{
@await Html.PartialAsync("_SupplyStore", "PlanRecord")
}
@await Html.PartialAsync("_SupplyStore", new SupplyStore { Tab = "PlanRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="planRecordType">@translator.Translate(userLanguage, "Type")</label>
<select class="form-select" id="planRecordType">
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Service")</!option>
@@ -108,7 +105,7 @@
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Plan Record")</button>
}
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
@await Html.PartialAsync("_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "planRecordCost" })
<script>
var uploadedFiles = [];
var selectedSupplies = [];

View File

@@ -21,7 +21,7 @@
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
@await Html.PartialAsync("_SupplyStore", "PlanRecordTemplate")
@await Html.PartialAsync("_SupplyStore", new SupplyStore { Tab = "PlanRecordTemplate", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="planRecordType">@translator.Translate(userLanguage, "Type")</label>
<select class="form-select" id="planRecordType">
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Service")</!option>
@@ -85,7 +85,7 @@
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="savePlanRecordTemplate(true)">@translator.Translate(userLanguage, "Edit Plan Record Template")</button>
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
@await Html.PartialAsync("_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "planRecordCost" })
<script>
var uploadedFiles = [];
var selectedSupplies = [];

View File

@@ -116,4 +116,16 @@
<div class="modal-content" id="planRecordTemplateSupplyOrderModalContent">
</div>
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><h6 class="dropdown-header context-menu-move move-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item context-menu-move move-planned" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Planned")</span><i class="bi bi-list-task"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-doing" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Doing")</span><i class="bi bi-tools"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-testing" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Testing")</span><i class="bi bi-eyeglasses"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-done" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Done")</span><i class="bi bi-check2-all"></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-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 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>

View File

@@ -7,6 +7,8 @@
var userLanguage = userConfig.UserLanguage;
var hasRefresh = Model.Where(x => (x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue) && x.IsRecurring).Any();
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var enableCsvImports = userConfig.EnableCsvImports;
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.ReminderRecord);
}
<div class="row">
<div class="d-flex justify-content-between">
@@ -28,7 +30,68 @@
</datalist>
</div>
<div>
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Reminder")</button>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Reminder")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('reminder-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='urgency' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Urgency" checked>
<label class="form-check-label stretched-link" for="chkCol_Urgency">@translator.Translate(userLanguage, "Urgency")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='metric' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Metric" checked>
<label class="form-check-label stretched-link" for="chkCol_Metric">@translator.Translate(userLanguage, "Metric")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Date">
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Odometer">
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
</ul>
</div>
}
else
{
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Reminder")</button>
}
</div>
</div>
</div>
@@ -42,39 +105,42 @@
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-1 text-truncate">@translator.Translate(userLanguage, "Urgency")</th>
<th scope="col" class="col-2 text-truncate">@translator.Translate(userLanguage, "Metric")</th>
<th scope="col" class="text-truncate @(hasRefresh ? "col-3 col-md-4" : "col-5")">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 col-md-3 text-truncate">@translator.Translate(userLanguage, "Notes")</th>
<th scope="col" data-column="urgency" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Urgency")</th>
<th scope="col" data-column="metric" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Metric")</th>
<th scope="col" data-column="date" style="display:none;" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" data-column="odometer" style="display:none;" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" data-column="description" class="flex-grow-1 flex-shrink-1 text-truncate @(hasRefresh ? "col-3" : "col-4")">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@translator.Translate(userLanguage, "Notes")</th>
@if (hasRefresh)
{
<th scope="col" class="col-2 col-md-1 text-truncate">@translator.Translate(userLanguage, "Done")</th>
<th scope="col" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Done")</th>
}
<th scope="col" class="col-2 col-md-1 text-truncate">@translator.Translate(userLanguage, "Delete")</th>
<th scope="col" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody>
@foreach (ReminderRecordViewModel reminderRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@reminderRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditReminderRecordModal,@reminderRecord.Id)" data-tags='@string.Join(" ", reminderRecord.Tags)'>
@if (reminderRecord.Urgency == ReminderUrgency.VeryUrgent)
{
<td class="col-1"><span class="text-danger d-inline-block d-md-none"><i class="bi bi-hourglass-split h3"></i></span><span class="badge text-bg-danger d-none d-md-inline-block">@translator.Translate(userLanguage, "Very Urgent")</span></td>
}
else if (reminderRecord.Urgency == ReminderUrgency.Urgent)
{
<td class="col-1"><span class="text-warning d-inline-block d-md-none"><i class="bi bi-hourglass-split h3"></i></span><span class="badge text-bg-warning d-none d-md-inline-block">@translator.Translate(userLanguage, "Urgent")</span></td>
}
else if (reminderRecord.Urgency == ReminderUrgency.PastDue)
{
<td class="col-1"><span class="text-secondary d-inline-block d-md-none"><i class="bi bi-hourglass-bottom h3"></i></span><span class="badge text-bg-secondary d-none d-md-inline-block">@translator.Translate(userLanguage, "Past Due")</span></td>
}
else
{
<td class="col-1"><span class="text-success d-inline-block d-md-none"><i class="bi bi-hourglass-top h3"></i></span><span class="badge text-bg-success d-none d-md-inline-block">@translator.Translate(userLanguage, "Not Urgent")</span></td>
}
<td class="col-2 text-truncate">
<span data-column="metric" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-html="true" data-bs-title="@($"<b><i class='bi bi-calendar-event me-2'></i></b>{reminderRecord.Date.ToShortDateString()}<b class='ms-2'><i class='bi bi-speedometer me-2'></i></b>{reminderRecord.Mileage}")">
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="urgency">
@if (reminderRecord.Urgency == ReminderUrgency.VeryUrgent)
{
<span class="badge text-bg-danger">@translator.Translate(userLanguage, "Very Urgent")</span>
}
else if (reminderRecord.Urgency == ReminderUrgency.Urgent)
{
<span class="badge text-bg-warning">@translator.Translate(userLanguage, "Urgent")</span>
}
else if (reminderRecord.Urgency == ReminderUrgency.PastDue)
{
<span class="badge text-bg-secondary">@translator.Translate(userLanguage, "Past Due")</span>
}
else
{
<span class="badge text-bg-success">@translator.Translate(userLanguage, "Not Urgent")</span>
}
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" data-column="metric">
@if (reminderRecord.Metric == ReminderMetric.Date)
{
@reminderRecord.Date.ToShortDateString()
@@ -87,30 +153,37 @@
{
@reminderRecord.Metric
}
</span>
</td>
<td class="text-truncate @(hasRefresh ? "col-3 col-md-4" : "col-5")" data-record-type='cost'>@reminderRecord.Description</td>
<td class="col-2 col-md-3 text-truncate">@StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" style="display:none;" data-column="date">
@(reminderRecord.UserMetric == ReminderMetric.Both || reminderRecord.UserMetric == ReminderMetric.Date ? reminderRecord.Date.ToShortDateString() : "---")
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" style="display:none;" data-column="odometer">
@(reminderRecord.UserMetric == ReminderMetric.Both || reminderRecord.UserMetric == ReminderMetric.Odometer ? reminderRecord.Mileage : "---")
</td>
<td data-column="description" class="flex-grow-1 flex-shrink-1 text-truncate @(hasRefresh ? "col-3" : "col-4")">@reminderRecord.Description</td>
<td data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
@if (hasRefresh)
{
<td class="col-2 col-md-1 text-truncate">
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">
@if((reminderRecord.Urgency == ReminderUrgency.VeryUrgent || reminderRecord.Urgency == ReminderUrgency.PastDue) && reminderRecord.IsRecurring)
{
<button type="button" class="btn btn-secondary" onclick="markDoneReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-check-lg"></i></button>
}
</td>
}
<td class="col-2 col-md-1 text-truncate">
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
</td>
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -122,16 +195,15 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ReminderRecord')"><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, 'ReminderRecord')"><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 text-danger" href="#" onclick="deleteRecords(selectedRow, 'ReminderRecord')"><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>
<script>
if (!getDeviceIsTouchOnly()) {
$("[data-column='metric']").map((x, y) => new bootstrap.Tooltip(y));
}
</script>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -44,10 +44,7 @@
}
<label for="serviceRecordCost">@translator.Translate(userLanguage,"Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="serviceRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the service")" value="@(isNew ? "" : Model.Cost)">
@if (isNew)
{
@await Html.PartialAsync("_SupplyStore", "ServiceRecord")
}
@await Html.PartialAsync("_SupplyStore", new SupplyStore { Tab = "ServiceRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="serviceRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="serviceRecordTag">
@foreach(string tag in Model.Tags)
@@ -126,7 +123,7 @@
<button type="button" class="btn btn-primary" onclick="saveServiceRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Service Record")</button>
}
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
@await Html.PartialAsync("_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "serviceRecordCost" })
<script>
var uploadedFiles = [];
var selectedSupplies = [];

View File

@@ -46,6 +46,13 @@
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('servicerecord-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
@@ -125,7 +132,7 @@
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(serviceRecord.Date)">@serviceRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(serviceRecord.Mileage == default ? "---" : serviceRecord.Mileage.ToString())</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@serviceRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(serviceRecord.Cost, hideZero))</td>
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1" data-column="notes">@StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -143,12 +150,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -162,21 +171,21 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'ServiceRecord', 'RepairRecord')">@translator.Translate(userLanguage, "Repairs")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'ServiceRecord', 'UpgradeRecord')">@translator.Translate(userLanguage, "Upgrades")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'ServiceRecord', 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Repairs")</span><i class="bi bi-exclamation-octagon"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'ServiceRecord', 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Upgrades")</span><i class="bi bi-wrench-adjustable"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ServiceRecord')"><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, 'ServiceRecord')"><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 text-danger" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')"><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><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</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>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'ServiceRecord')"><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>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -102,7 +102,7 @@
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Supply Record")</button>
}
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
@await Html.PartialAsync("_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "" })
<script>
var uploadedFiles = [];
getUploadedFilesFromModel();

View File

@@ -46,6 +46,13 @@
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('supply-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
@@ -141,7 +148,7 @@
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="supplier">@supplyRecord.PartSupplier</td>
<td class="col-2 flex-grow-1 col-xl-3 text-truncate" data-column="description">@supplyRecord.Description</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="quantity">@supplyRecord.Quantity</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(supplyRecord.Cost, hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -161,12 +168,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -180,12 +189,12 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'SupplyRecord')"><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, 'SupplyRecord')"><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 text-danger" href="#" onclick="deleteRecords(selectedRow, 'SupplyRecord')"><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>
@if (userColumnPreferences.Any())
{

View File

@@ -1,20 +1,46 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model SupplyRequisitionHistory
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var showDelete = Model.RequisitionHistory.All(x => x.Id != default);
var showPartNumber = Model.RequisitionHistory.Any(x => !string.IsNullOrWhiteSpace(x.PartNumber));
}
@model List<SupplyUsageHistory>
<script>
var supplyUsageHistory = [];
var deletedSupplyUsageHistory = [];
function subtractFromCostInput(costToSubtract){
let costInputId = '@Model.CostInputId';
let costInput = $(`#${costInputId}`);
let newCostAmount = globalParseFloat(costInput.val()) - globalParseFloat(costToSubtract);
if (newCostAmount < 0){
newCostAmount = 0;
}
costInput.val(globalFloatToString(newCostAmount.toFixed(2)));
}
function deleteSupplyUsageHistory(supplyId, quantity, cost, supplyRow){
deletedSupplyUsageHistory.push({
id: decodeHTMLEntities(supplyId),
quantity: decodeHTMLEntities(quantity),
cost: decodeHTMLEntities(cost)
});
let supplyIndexToRemove = supplyUsageHistory.findIndex(x=>x.id == decodeHTMLEntities(supplyId) && x.quantity == decodeHTMLEntities(quantity) && x.cost == decodeHTMLEntities(cost));
if (supplyIndexToRemove > -1){
supplyUsageHistory.splice(supplyIndexToRemove, 1);
}
$(supplyRow).parents(".supply-row").remove();
//update cost input value
subtractFromCostInput(decodeHTMLEntities(cost));
}
</script>
<div id="supplyUsageHistoryModalContainer" class="d-none">
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage, "Supply Requisition History")</h5>
</div>
<div class="modal-body">
@if (Model.Any())
@if (Model.RequisitionHistory.Any())
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
@@ -22,35 +48,43 @@
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Date")</th>
@if(Model.Any(x=>!string.IsNullOrWhiteSpace(x.PartNumber))){
@if(showPartNumber){
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="@(showDelete ? "col-2" : "col-4")">@translator.Translate(userLanguage, "Description")</th>
} else
{
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="@(showDelete ? "col-4" : "col-6")">@translator.Translate(userLanguage, "Description")</th>
}
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Quantity")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Cost")</th>
@if (showDelete){
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
}
</tr>
</thead>
<tbody>
@foreach (SupplyUsageHistory usageHistory in Model)
@foreach (SupplyUsageHistory usageHistory in Model.RequisitionHistory)
{
<script>
supplyUsageHistory.push({ date: decodeHTMLEntities("@usageHistory.Date.ToShortDateString()"), partNumber: decodeHTMLEntities('@usageHistory.PartNumber'), description: decodeHTMLEntities("@usageHistory.Description"), quantity: decodeHTMLEntities("@usageHistory.Quantity.ToString("F")"), cost: decodeHTMLEntities("@usageHistory.Cost.ToString("F")") })
supplyUsageHistory.push({ id: decodeHTMLEntities("@usageHistory.Id"), date: decodeHTMLEntities("@usageHistory.Date.ToShortDateString()"), partNumber: decodeHTMLEntities('@usageHistory.PartNumber'), description: decodeHTMLEntities("@usageHistory.Description"), quantity: decodeHTMLEntities("@usageHistory.Quantity.ToString("F")"), cost: decodeHTMLEntities("@usageHistory.Cost.ToString("F")") })
</script>
<tr class="d-flex">
<tr class="d-flex supply-row">
<td class="col-2">@StaticHelper.TruncateStrings(usageHistory.Date.ToShortDateString())</td>
@if (!string.IsNullOrWhiteSpace(usageHistory.PartNumber))
@if (showPartNumber)
{
<td class="col-2 text-truncate">@StaticHelper.TruncateStrings(usageHistory.PartNumber)</td>
<td class="col-4 text-truncate">@StaticHelper.TruncateStrings(usageHistory.Description)</td>
<td class="@(showDelete ? "col-2" : "col-4") text-truncate">@StaticHelper.TruncateStrings(usageHistory.Description)</td>
} else
{
<td class="col-6 text-truncate">@StaticHelper.TruncateStrings(usageHistory.Description, 50)</td>
<td class="@(showDelete ? "col-4" : "col-6") text-truncate">@StaticHelper.TruncateStrings(usageHistory.Description, 50)</td>
}
<td class="col-2">@usageHistory.Quantity.ToString("F")</td>
<td class="col-2">@usageHistory.Cost.ToString("C2")</td>
@if (showDelete){
<td class="col-2">
<button type="button" class="btn btn-danger" onclick="deleteSupplyUsageHistory('@usageHistory.Id.ToString()', '@usageHistory.Quantity.ToString("F")', '@usageHistory.Cost.ToString("F")', this)"><i class="bi bi-trash"></i></button>
</td>
}
</tr>
}
</tbody>

View File

@@ -5,36 +5,51 @@
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model string
@model SupplyStore
<div class="row">
<div class="col-12">
<a onclick="showSuppliesModal()" class="btn btn-link">@translator.Translate(userLanguage,"Choose Supplies")</a>
<a onclick="showSuppliesModal()" class="btn btn-link">@translator.Translate(userLanguage,Model.AdditionalSupplies ? "Choose Additional Supplies" : "Choose Supplies")</a>
</div>
</div>
<script>
resetSuppliesModal();
function GetCaller() {
return { tab: '@Model' };
return {
tab: '@Model.Tab',
addToSum: @Model.AdditionalSupplies.ToString().ToLower()
};
}
function resetSuppliesModal() {
$("#inputSuppliesModalContent").html("");
}
function setCostInputWithSupplySum(selectedSum, input){
var addToSum = GetCaller().addToSum;
if (addToSum){
//sum of all requisitioned supplies
var currentSum = supplyUsageHistory.length > 0 ? supplyUsageHistory.map(x => globalParseFloat(x.cost)).reduce((a,b) => a + b) : 0;
selectedSum = globalParseFloat(selectedSum);
var newSum = currentSum + selectedSum;
input.val(globalFloatToString(newSum.toFixed(2)));
} else {
input.val(selectedSum);
}
}
function selectSupplies() {
var selectedSupplyResult = getSuppliesAndQuantity();
var caller = GetCaller().tab;
switch (caller) {
case "ServiceRecord":
$('#serviceRecordCost').val(selectedSupplyResult.totalSum);
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#serviceRecordCost'))
break;
case "RepairRecord":
$('#collisionRecordCost').val(selectedSupplyResult.totalSum);
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#collisionRecordCost'))
break;
case "UpgradeRecord":
$('#upgradeRecordCost').val(selectedSupplyResult.totalSum);
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#upgradeRecordCost'))
break;
case "PlanRecord":
case "PlanRecordTemplate":
$('#planRecordCost').val(selectedSupplyResult.totalSum);
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#planRecordCost'))
break;
}
selectedSupplies = getSuppliesAndQuantity().selectedSupplies;

View File

@@ -17,7 +17,7 @@
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;" id="supplies-table">
<div class="alert alert-warning" role="alert">
@translator.Translate(userLanguage,"Supplies are requisitioned immediately after the record is created and cannot be modified. If you have incorrectly entered the amount you needed you will need to correct it in the Supplies tab.")
@translator.Translate(userLanguage,"Supplies are requisitioned immediately after the record is saved.")
</div>
<div class="d-flex align-items-center flex-wrap">
@foreach (string recordTag in recordTags)

View File

@@ -46,6 +46,13 @@
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('tax-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
@@ -117,7 +124,7 @@
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@taxRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditTaxRecordModal,@taxRecord.Id)" data-tags='@string.Join(" ", taxRecord.Tags)'>
<td class="col-3 flex-grow-1 col-xl-1 text-truncate" data-column="date">@taxRecord.Date.ToShortDateString()</td>
<td class="col-4 flex-grow-1 col-xl-6 text-truncate" data-column="description">@taxRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(taxRecord.Cost, hideZero))</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(taxRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -137,12 +144,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -156,12 +165,12 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'TaxRecord')"><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, 'TaxRecord')"><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 text-danger" href="#" onclick="deleteRecords(selectedRow, 'TaxRecord')"><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>
@if (userColumnPreferences.Any())
{

View File

@@ -44,10 +44,7 @@
}
<label for="upgradeRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="upgradeRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the upgrade/mods")" value="@(isNew ? "" : Model.Cost)">
@if (isNew)
{
@await Html.PartialAsync("_SupplyStore", "UpgradeRecord")
}
@await Html.PartialAsync("_SupplyStore", new SupplyStore { Tab = "UpgradeRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="upgradeRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="upgradeRecordTag">
@foreach (string tag in Model.Tags)
@@ -126,7 +123,7 @@
<button type="button" class="btn btn-primary" onclick="saveUpgradeRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Upgrade Record")</button>
}
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
@await Html.PartialAsync("_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "upgradeRecordCost" })
<script>
var uploadedFiles = [];
var selectedSupplies = [];

View File

@@ -46,6 +46,13 @@
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('upgrade-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
@@ -125,7 +132,7 @@
<td class="col-2 flex-grow-1 col-xl-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(upgradeRecord.Date)">@upgradeRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(upgradeRecord.Mileage == default ? "---" : upgradeRecord.Mileage.ToString())</td>
<td class="col-3 flex-grow-1 flex-shrink-1 col-xl-4 text-truncate" data-column="description">@upgradeRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && upgradeRecord.Cost == default) ? "---" : upgradeRecord.Cost.ToString("C"))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(upgradeRecord.Cost, hideZero))</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -145,12 +152,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>
@@ -163,21 +172,21 @@
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'UpgradeRecord', 'ServiceRecord')">@translator.Translate(userLanguage, "Service Records")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'UpgradeRecord', 'RepairRecord')">@translator.Translate(userLanguage, "Repairs")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'UpgradeRecord', 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Service Records")</span><i class="bi bi-card-checklist"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'UpgradeRecord', 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Repairs")</span><i class="bi bi-exclamation-octagon"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'UpgradeRecord')"><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, 'UpgradeRecord')"><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 text-danger" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')"><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><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</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>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'UpgradeRecord')"><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>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -145,7 +145,7 @@
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Date)) ? "" : "d-none")">@reportData.Date.ToShortDateString()</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Odometer)) ? "" : "d-none")">@(reportData.Odometer == default ? "---" : reportData.Odometer.ToString("N0"))</td>
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Description)) ? "" : "d-none")">@reportData.Description</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Cost)) ? "" : "d-none")">@((hideZero && reportData.Cost == default) ? "---" : reportData.Cost.ToString("C"))</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Cost)) ? "" : "d-none")">@(StaticHelper.HideZeroCost(reportData.Cost, hideZero))</td>
<td class="col-4 flex-grow-1 flex-shrink-1 text-wrap text-break @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Notes)) ? "" : "d-none")">@StaticHelper.TruncateStrings(reportData.Notes, 100)</td>
@foreach(string extraField in extraFields)
{
@@ -153,12 +153,14 @@
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tbody>
</tfoot>
</table>
</div>
</div>

View File

@@ -22,6 +22,7 @@
"EnableExtraFieldColumns": false,
"UseUKMPG": false,
"UseThreeDecimalGasCost": true,
"UseThreeDecimalGasConsumption": true,
"UseMarkDownOnSavedNotes": false,
"HideSoldVehicles": false,
"PreferredGasMileageUnit": "",

View File

@@ -211,6 +211,11 @@ html {
}
}
.btn-check:checked + .dropdown-item, :not(.btn-check) + .dropdown-item:active {
color: #fff;
background-color: #0d6efd;
}
/*Media Queries*/
@media (max-width: 576px) {
.lubelogger-tab {
@@ -282,6 +287,20 @@ html {
.table-context-menu {
z-index: 1030;
box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24);
position: absolute;
}
html[data-bs-theme="dark"] .table-context-menu {
background-color: rgba(33, 37, 41, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
html[data-bs-theme="light"] .table-context-menu {
background-color: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
input[type="file"] {

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -149,6 +149,7 @@ function getAndValidateCollisionRecordValues() {
addReminderRecord: addReminderRecord,
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory,
deletedRequisitionHistory: deletedSupplyUsageHistory,
reminderRecordId: recurringReminderRecordId,
copySuppliesAttachment: copySuppliesAttachments
}

View File

@@ -267,6 +267,7 @@ function convertGasConsumptionUnits(currentUnit, destinationUnit, save) {
break;
}
}
updateMPGLabels();
}
function convertFuelMileageUnits(currentUnit, destinationUnit, save) {
@@ -281,31 +282,6 @@ function convertFuelMileageUnits(currentUnit, destinationUnit, save) {
elem.innerText = globalFloatToString(convertedAmount.toFixed(2));
}
});
//update labels up top.
if ($("#averageFuelMileageLabel").length > 0) {
var newAverage = globalParseFloat($("#averageFuelMileageLabel").text().split(":")[1].trim());
if (newAverage > 0) {
newAverage = 100 / newAverage;
var averageLabel = $("#averageFuelMileageLabel");
averageLabel.text(`${averageLabel.text().split(':')[0]}: ${globalFloatToString(newAverage.toFixed(2))}`);
}
}
if ($("#minFuelMileageLabel").length > 0) {
var newMin = globalParseFloat($("#minFuelMileageLabel").text().split(":")[1].trim());
if (newMin > 0) {
newMin = 100 / newMin;
var minLabel = $("#minFuelMileageLabel");
minLabel.text(`${minLabel.text().split(':')[0]}: ${globalFloatToString(newMin.toFixed(2))}`);
}
}
if ($("#maxFuelMileageLabel").length > 0) {
var newMax = globalParseFloat($("#maxFuelMileageLabel").text().split(":")[1].trim());
if (newMax > 0) {
newMax = 100 / newMax;
var maxLabel = $("#maxFuelMileageLabel");
maxLabel.text(`${maxLabel.text().split(':')[0]}: ${globalFloatToString(newMax.toFixed(2))}`);
}
}
sender.text(sender.text().replace(sender.attr("data-unit"), "km/l"));
sender.attr("data-unit", "km/l");
if (save) { setDebounce(saveUserGasTabPreferences); }
@@ -321,36 +297,13 @@ function convertFuelMileageUnits(currentUnit, destinationUnit, save) {
elem.innerText = globalFloatToString(convertedAmount.toFixed(2));
}
});
if ($("#averageFuelMileageLabel").length > 0) {
var newAverage = globalParseFloat($("#averageFuelMileageLabel").text().split(":")[1].trim());
if (newAverage > 0) {
newAverage = 100 / newAverage;
var averageLabel = $("#averageFuelMileageLabel");
averageLabel.text(`${averageLabel.text().split(':')[0]}: ${globalFloatToString(newAverage.toFixed(2))}`);
}
}
if ($("#minFuelMileageLabel").length > 0) {
var newMin = globalParseFloat($("#minFuelMileageLabel").text().split(":")[1].trim());
if (newMin > 0) {
newMin = 100 / newMin;
var minLabel = $("#minFuelMileageLabel");
minLabel.text(`${minLabel.text().split(':')[0]}: ${globalFloatToString(newMin.toFixed(2))}`);
}
}
if ($("#maxFuelMileageLabel").length > 0) {
var newMax = globalParseFloat($("#maxFuelMileageLabel").text().split(":")[1].trim());
if (newMax > 0) {
newMax = 100 / newMax;
var maxLabel = $("#maxFuelMileageLabel");
maxLabel.text(`${maxLabel.text().split(':')[0]}: ${globalFloatToString(newMax.toFixed(2))}`);
}
}
sender.text(sender.text().replace(sender.attr("data-unit"), "l/100km"));
sender.attr("data-unit", "l/100km");
if (save) { setDebounce(saveUserGasTabPreferences); }
break;
}
}
updateMPGLabels();
}
function toggleGasFilter(sender) {
filterTable('gas-tab-pane', sender);
@@ -360,13 +313,17 @@ function updateMPGLabels() {
var averageLabel = $("#averageFuelMileageLabel");
var minLabel = $("#minFuelMileageLabel");
var maxLabel = $("#maxFuelMileageLabel");
if (averageLabel.length > 0 && minLabel.length > 0 && maxLabel.length > 0) {
var totalConsumedLabel = $("#totalFuelConsumedLabel");
if (averageLabel.length > 0 && minLabel.length > 0 && maxLabel.length > 0 && totalConsumedLabel.length > 0) {
var rowsToAggregate = $("[data-aggregated='true']").parent(":not('.override-hide')");
var rowsUnaggregated = $("[data-aggregated='false']").parent(":not('.override-hide')");
var rowMPG = rowsToAggregate.children('[data-gas-type="fueleconomy"]').toArray().map(x => globalParseFloat(x.textContent));
var maxMPG = rowMPG.length > 0 ? rowMPG.reduce((a, b) => a > b ? a : b) : 0;
var minMPG = rowMPG.length > 0 ? rowMPG.filter(x=>x>0).reduce((a, b) => a < b ? a : b) : 0;
var totalMilesTraveled = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
var totalGasConsumed = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
var totalGasConsumed = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
var totalUnaggregatedGasConsumed = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
var fullGasConsumed = totalGasConsumed + totalUnaggregatedGasConsumed;
if (totalGasConsumed > 0) {
var averageMPG = totalMilesTraveled / totalGasConsumed;
if (!getGlobalConfig().useMPG && $("[data-gas='fueleconomy']").attr("data-unit") != 'km/l' && averageMPG > 0) {
@@ -374,7 +331,12 @@ function updateMPGLabels() {
}
averageLabel.text(`${averageLabel.text().split(':')[0]}: ${globalFloatToString(averageMPG.toFixed(2))}`);
} else {
averageLabel.text(`${averageLabel.text().split(':')[0]}: 0.00`);
averageLabel.text(`${averageLabel.text().split(':')[0]}: ${globalFloatToString('0.00')}`);
}
if (fullGasConsumed > 0) {
totalConsumedLabel.text(`${totalConsumedLabel.text().split(':')[0]}: ${globalFloatToString(fullGasConsumed.toFixed(2))}`);
} else {
totalConsumedLabel.text(`${totalConsumedLabel.text().split(':')[0]}: ${globalFloatToString('0.00')}`);
}
if (!getGlobalConfig().useMPG && $("[data-gas='fueleconomy']").attr("data-unit") != 'km/l') {
maxLabel.text(`${maxLabel.text().split(':')[0]}: ${globalFloatToString(minMPG.toFixed(2))}`);
@@ -455,6 +417,9 @@ function searchGasTableRows() {
});
}
function editMultipleGasRecords(ids) {
if (ids.length < 2) {
return;
}
$.post('/Vehicle/GetGasRecordsEditModal', { recordIds: ids }, function (data) {
if (data) {
$("#gasRecordModalContent").html(data);

View File

@@ -152,6 +152,9 @@ function recalculateDistance() {
}
function editMultipleOdometerRecords(ids) {
if (ids.length < 2) {
return;
}
$.post('/Vehicle/GetOdometerRecordsEditModal', { recordIds: ids }, function (data) {
if (data) {
$("#odometerRecordModalContent").html(data);

View File

@@ -250,6 +250,7 @@ function getAndValidatePlanRecordValues() {
importMode: planType,
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory,
deletedRequisitionHistory: deletedSupplyUsageHistory,
reminderRecordId: reminderRecordId,
copySuppliesAttachment: copySuppliesAttachments
}
@@ -268,12 +269,16 @@ function dragStart(event, planRecordId) {
}
function dragOver(event) {
event.preventDefault();
if (planTouchTimer) {
clearTimeout(planTouchTimer);
planTouchTimer = null;
}
}
function dropBox(event, newProgress) {
if ($(event.target).hasClass("swimlane")) {
if (dragged.parentElement != event.target && event.target != dragged) {
updatePlanRecordProgress(newProgress);
}
var targetSwimLane = $(event.target).hasClass("swimlane") ? event.target : $(event.target).parents(".swimlane")[0];
var draggedSwimLane = $(dragged).parents(".swimlane")[0];
if (targetSwimLane != draggedSwimLane) {
updatePlanRecordProgress(newProgress);
}
event.preventDefault();
}
@@ -348,4 +353,105 @@ function orderPlanSupplies(planRecordTemplateId, closeSwal) {
function hideOrderSupplyModal() {
$("#planRecordTemplateSupplyOrderModal").modal('hide');
showPlanRecordTemplatesModal();
}
function configurePlanTableContextMenu(planRecordId, currentSwimLane) {
//clear any bound actions
$(".context-menu-move").off('click');
//bind context menu actions
$(".context-menu-delete").on('click', () => {
deletePlanRecord(planRecordId, true);
});
let planRecordIdArray = [planRecordId];
$(".context-menu-duplicate").on('click', () => {
duplicateRecords(planRecordIdArray, 'PlanRecord');
});
$(".context-menu-duplicate-vehicle").on('click', () => {
duplicateRecordsToOtherVehicles(planRecordIdArray, 'PlanRecord');
});
$(".context-menu-move.move-planned").on('click', () => {
draggedId = planRecordId;
updatePlanRecordProgress('Backlog');
draggedId = 0;
});
$(".context-menu-move.move-doing").on('click', () => {
draggedId = planRecordId;
updatePlanRecordProgress('InProgress');
});
$(".context-menu-move.move-testing").on('click', () => {
draggedId = planRecordId;
updatePlanRecordProgress('Testing');
});
$(".context-menu-move.move-done").on('click', () => {
draggedId = planRecordId;
updatePlanRecordProgress('Done');
});
//hide all move buttons
$(".context-menu-move").hide();
$(".context-menu-delete").show(); //delete is always visible.
switch (currentSwimLane) {
case 'Backlog':
$(".context-menu-move.move-header").show();
$(".context-menu-move.move-doing").show();
$(".context-menu-move.move-testing").show();
$(".context-menu-move.move-done").show();
break;
case 'InProgress':
$(".context-menu-move.move-header").show();
$(".context-menu-move.move-planned").show();
$(".context-menu-move.move-testing").show();
$(".context-menu-move.move-done").show();
break;
case 'Testing':
$(".context-menu-move.move-header").show();
$(".context-menu-move.move-planned").show();
$(".context-menu-move.move-doing").show();
$(".context-menu-move.move-done").show();
break;
case 'Done':
break;
}
}
function showPlanTableContextMenu(e, planRecordId, currentSwimLane) {
if (event != undefined) {
event.preventDefault();
}
if (getDeviceIsTouchOnly()) {
return;
}
if (planRecordId == 0) {
return;
}
$(".table-context-menu").fadeIn("fast");
$(".table-context-menu").css({
left: getMenuPosition(event.clientX, 'width', 'scrollLeft'),
top: getMenuPosition(event.clientY, 'height', 'scrollTop')
});
configurePlanTableContextMenu(planRecordId, currentSwimLane);
}
function showPlanTableContextMenuForMobile(e, xPosition, yPosition, planRecordId, currentSwimLane) {
$(".table-context-menu").fadeIn("fast");
$(".table-context-menu").css({
left: getMenuPosition(xPosition, 'width', 'scrollLeft'),
top: getMenuPosition(yPosition, 'height', 'scrollTop')
});
configurePlanTableContextMenu(planRecordId, currentSwimLane);
if (planTouchTimer) {
clearTimeout(planTouchTimer);
planTouchTimer = null;
}
}
var planTouchTimer;
var planTouchDuration = 3000;
function detectPlanItemLongTouch(sender, planRecordId, currentSwimLane) {
var touchX = event.touches[0].clientX;
var touchY = event.touches[0].clientY;
if (!planTouchTimer) {
planTouchTimer = setTimeout(function () { showPlanTableContextMenuForMobile(sender, touchX, touchY, planRecordId, currentSwimLane); detectPlanItemTouchEndPremature(sender); }, planTouchDuration);
}
}
function detectPlanItemTouchEndPremature(sender) {
if (planTouchTimer) {
clearTimeout(planTouchTimer);
planTouchTimer = null;
}
}

View File

@@ -25,10 +25,28 @@ function getAndValidateSelectedColumns() {
}
}
}
function getSavedReportParameters() {
var vehicleId = GetVehicleId().vehicleId;
var selectedReportColumns = sessionStorage.getItem(`${vehicleId}_selectedReportColumns`);
if (selectedReportColumns != null) {
selectedReportColumns = JSON.parse(selectedReportColumns);
//unselected everything
$(".column-extrafield").prop('checked', false);
$(".column-default").prop('checked', false);
//load selected checkboxes
selectedReportColumns.extraFields.map(x => {
$(`[value='${x}'].column-extrafield`).prop('checked', true);
})
selectedReportColumns.visibleColumns.map(x => {
$(`[value='${x}'].column-default`).prop('checked', true);
})
}
}
function generateVehicleHistoryReport() {
var vehicleId = GetVehicleId().vehicleId;
$.get(`/Vehicle/GetReportParameters`, function (data) {
if (data) {
//prompt user to select a vehicle
//prompt user to select columns
Swal.fire({
title: 'Select Columns',
html: data,
@@ -42,9 +60,14 @@ function generateVehicleHistoryReport() {
}
return { selectedColumnsData }
},
didOpen: () => {
getSavedReportParameters();
}
}).then(function (result) {
if (result.isConfirmed) {
var vehicleId = GetVehicleId().vehicleId;
//save params in sessionStorage
sessionStorage.setItem(`${vehicleId}_selectedReportColumns`, JSON.stringify(result.value.selectedColumnsData));
//post params
$.post(`/Vehicle/GetVehicleHistory?vehicleId=${vehicleId}`, {
reportParameter: result.value.selectedColumnsData
}, function (data) {
@@ -86,14 +109,16 @@ function setSelectedMetrics() {
});
var yearMetric = $('#yearOption').val();
var reminderMetric = $("#reminderOption").val();
sessionStorage.setItem("selectedMetricCheckBoxes", JSON.stringify(selectedMetricCheckBoxes));
sessionStorage.setItem("yearMetric", yearMetric);
sessionStorage.setItem("reminderMetric", reminderMetric);
var vehicleId = GetVehicleId().vehicleId;
sessionStorage.setItem(`${vehicleId}_selectedMetricCheckBoxes`, JSON.stringify(selectedMetricCheckBoxes));
sessionStorage.setItem(`${vehicleId}_yearMetric`, yearMetric);
sessionStorage.setItem(`${vehicleId}_reminderMetric`, reminderMetric);
}
function getSelectedMetrics() {
var selectedMetricCheckBoxes = sessionStorage.getItem("selectedMetricCheckBoxes");
var yearMetric = sessionStorage.getItem("yearMetric");
var reminderMetric = sessionStorage.getItem("reminderMetric");
var vehicleId = GetVehicleId().vehicleId;
var selectedMetricCheckBoxes = sessionStorage.getItem(`${vehicleId}_selectedMetricCheckBoxes`);
var yearMetric = sessionStorage.getItem(`${vehicleId}_yearMetric`);
var reminderMetric = sessionStorage.getItem(`${vehicleId}_reminderMetric`);
if (selectedMetricCheckBoxes != null && yearMetric != null && reminderMetric != null) {
selectedMetricCheckBoxes = JSON.parse(selectedMetricCheckBoxes);
$(".reportCheckBox").prop('checked', false);
@@ -151,6 +176,66 @@ function refreshBarChart() {
});
setSelectedMetrics();
}
function showBarChartTable(elemClicked) {
var selectedMetrics = [];
var vehicleId = GetVehicleId().vehicleId;
var year = getYear();
if ($("#serviceExpenseCheck").is(":checked")) {
selectedMetrics.push('ServiceRecord');
}
if ($("#repairExpenseCheck").is(":checked")) {
selectedMetrics.push('RepairRecord');
}
if ($("#upgradeExpenseCheck").is(":checked")) {
selectedMetrics.push('UpgradeRecord');
}
if ($("#gasExpenseCheck").is(":checked")) {
selectedMetrics.push('GasRecord');
}
if ($("#taxExpenseCheck").is(":checked")) {
selectedMetrics.push('TaxRecord');
}
if ($("#odometerExpenseCheck").is(":checked")) {
selectedMetrics.push('OdometerRecord');
}
$.post('/Vehicle/GetCostByMonthAndYearByVehicle',
{
vehicleId: vehicleId,
selectedMetrics: selectedMetrics,
year: year
}, function (data) {
$("#vehicleDataTableModalContent").html(data);
$("#vehicleDataTableModal").modal('show');
//highlight clicked row.
if (elemClicked.length > 0) {
var rowClickedIndex = elemClicked[0].index + 1;
var rowToHighlight = $("#vehicleDataTableModalContent").find(`tbody > tr:nth-child(${rowClickedIndex})`);
if (rowToHighlight.length > 0) {
rowToHighlight.addClass('table-info');
}
}
});
}
function toggleBarChartTableData() {
//find out which column data type is shown
if (!$('[report-data="cost"]').hasClass('d-none')) {
//currently cost is shown.
$('[report-data="cost"]').addClass('d-none');
$('[report-data="distance"]').removeClass('d-none');
}
else if (!$('[report-data="distance"]').hasClass('d-none')) {
//currently distance is shown.
$('[report-data="distance"]').addClass('d-none');
$('[report-data="costperdistance"]').removeClass('d-none');
}
else if (!$('[report-data="costperdistance"]').hasClass('d-none')) {
//currently cost per distance is shown.
$('[report-data="costperdistance"]').addClass('d-none');
$('[report-data="cost"]').removeClass('d-none');
}
}
function updateReminderPie() {
var vehicleId = GetVehicleId().vehicleId;
var daysToAdd = $("#reminderOption").val();
@@ -234,12 +319,19 @@ function exportAttachments() {
}
});
}
function showDataTable() {
function showDataTable(elemClicked) {
var vehicleId = GetVehicleId().vehicleId;
var year = getYear();
$.get(`/Vehicle/GetCostTableForVehicle?vehicleId=${vehicleId}`, { year: year }, function (data) {
$("#vehicleDataTableModalContent").html(data);
$("#vehicleDataTableModal").modal('show');
if (elemClicked.length > 0) {
var rowClickedIndex = elemClicked[0].index + 1;
var rowToHighlight = $("#vehicleDataTableModalContent").find(`tbody > tr:nth-child(${rowClickedIndex})`);
if (rowToHighlight.length > 0) {
rowToHighlight.addClass('table-info');
}
}
});
}
function hideDataTable() {

View File

@@ -149,6 +149,7 @@ function getAndValidateServiceRecordValues() {
addReminderRecord: addReminderRecord,
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory,
deletedRequisitionHistory: deletedSupplyUsageHistory,
reminderRecordId: recurringReminderRecordId,
copySuppliesAttachment: copySuppliesAttachments
}

View File

@@ -57,6 +57,7 @@ function updateSettings() {
automaticDecimalFormat: $("#automaticDecimalFormat").is(":checked"),
useUKMpg: $("#useUKMPG").is(":checked"),
useThreeDecimalGasCost: $("#useThreeDecimal").is(":checked"),
useThreeDecimalGasConsumption: $("#useThreeDecimalGasConsumption").is(":checked"),
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),

View File

@@ -479,7 +479,7 @@ function updateAggregateLabels() {
var sumLabel = $("[data-aggregate-type='sum']");
if (sumLabel.length > 0) {
var labelsToSum = $("[data-record-type='cost']").parent(":not('.override-hide')").children("[data-record-type='cost']").toArray();
var newSum = 0;
var newSum = "0.00";
if (labelsToSum.length > 0) {
newSum = labelsToSum.map(x => globalParseFloat(x.textContent)).reduce((a, b,) => a + b).toFixed(2);
}
@@ -825,6 +825,10 @@ function duplicateRecords(ids, source) {
friendlySource = "Fuel Records";
refreshDataCallBack = getVehicleGasRecords;
break;
case "PlanRecord":
friendlySource = "Plan";
refreshDataCallBack = getVehiclePlanRecords;
break;
}
Swal.fire({
@@ -894,6 +898,10 @@ function duplicateRecordsToOtherVehicles(ids, source) {
friendlySource = "Fuel Records";
refreshDataCallBack = getVehicleGasRecords;
break;
case "PlanRecord":
friendlySource = "Plan";
refreshDataCallBack = getVehiclePlanRecords;
break;
}
$.get(`/Home/GetVehicleSelector?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
@@ -960,8 +968,9 @@ function rangeMouseDown(e) {
if (isRightClick(e)) {
return;
}
var contextMenuAction = $(e.target).is(".table-context-menu > li > .dropdown-item")
if (!(e.ctrlKey || e.metaKey) && !contextMenuAction) {
var contextMenuAction = $(e.target).parents(".table-context-menu > li > .dropdown-item").length > 0 || $(e.target).is(".table-context-menu > li > .dropdown-item");
var selectMode = $("#chkSelectMode").length > 0 ? $("#chkSelectMode").is(":checked") : false;
if (!(e.ctrlKey || e.metaKey || selectMode) && !contextMenuAction) {
clearSelectedRows();
}
isDragging = true;
@@ -984,7 +993,7 @@ function stopEvent() {
}
function rangeMouseUp(e) {
if ($(".table-context-menu").length > 0) {
$(".table-context-menu").hide();
$(".table-context-menu").fadeOut("fast");
}
if (isRightClick(e)) {
return;
@@ -1029,10 +1038,9 @@ function showTableContextMenu(e) {
if (getDeviceIsTouchOnly()) {
return;
}
$(".table-context-menu").show();
$(".table-context-menu").fadeIn("fast");
determineContextMenuItems();
$(".table-context-menu").css({
position: "absolute",
left: getMenuPosition(event.clientX, 'width', 'scrollLeft'),
top: getMenuPosition(event.clientY, 'height', 'scrollTop')
});
@@ -1090,7 +1098,8 @@ function getMenuPosition(mouse, direction, scrollDir) {
return position;
}
function handleTableRowClick(e, callBack, rowId) {
if (!(event.ctrlKey || event.metaKey)) {
var selectMode = $("#chkSelectMode").length > 0 ? $("#chkSelectMode").is(":checked") : false;
if (!(event.ctrlKey || event.metaKey || selectMode)) {
callBack(rowId);
} else if (!$(e).hasClass('table-active')) {
addToSelectedRows($(e).attr('data-rowId'));
@@ -1107,13 +1116,12 @@ function showTableContextMenuForMobile(e, xPosition, yPosition) {
$(e).addClass('table-active');
shakeTableRow(e);
} else {
$(".table-context-menu").show();
determineContextMenuItems();
$(".table-context-menu").fadeIn("fast");
$(".table-context-menu").css({
position: "absolute",
left: getMenuPosition(xPosition, 'width', 'scrollLeft'),
top: getMenuPosition(yPosition, 'height', 'scrollTop')
});
determineContextMenuItems();
}
}
function shakeTableRow(e) {

View File

@@ -149,6 +149,7 @@ function getAndValidateUpgradeRecordValues() {
addReminderRecord: addReminderRecord,
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory,
deletedRequisitionHistory: deletedSupplyUsageHistory,
reminderRecordId: recurringReminderRecordId,
copySuppliesAttachment: copySuppliesAttachments
}

View File

@@ -368,6 +368,9 @@ function showRecurringReminderSelector(descriptionFieldName) {
})
}
function editMultipleRecords(ids, dataType) {
if (ids.length < 2) {
return;
}
$.post('/Vehicle/GetGenericRecordModal', { recordIds: ids, dataType: dataType }, function (data) {
if (data) {
$("#genericRecordEditModalContent").html(data);
@@ -549,6 +552,7 @@ function adjustRecordsOdometer(ids, source) {
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
saveScrollPosition();
$.post('/Vehicle/AdjustRecordsOdometer', { recordIds: ids, vehicleId: GetVehicleId().vehicleId, importMode: source }, function (data) {
if (data) {
successToast(`${ids.length} Record(s) Updated`);