Compare commits

..

26 Commits

Author SHA1 Message Date
Hargata Softworks
98b1cd9bc5 Merge pull request #315 from hargata/Hargata/linked.planner.to.reminder
check if reminder still exists.
2024-02-18 15:04:45 -07:00
DESKTOP-GENO133\IvanPlex
a02684f921 check if reminder still exists. 2024-02-18 15:04:02 -07:00
Hargata Softworks
13ddc6309e Merge pull request #314 from hargata/Hargata/linked.planner.to.reminder
Updated version number
2024-02-18 14:42:42 -07:00
DESKTOP-GENO133\IvanPlex
2b2fd3ae28 Updated version number 2024-02-18 14:42:05 -07:00
Hargata Softworks
2dd3dc1718 Merge pull request #313 from hargata/Hargata/linked.planner.to.reminder
added ability to create plans from reminders.
2024-02-18 14:23:39 -07:00
DESKTOP-GENO133\IvanPlex
fe1b96d58c updated translation 2024-02-18 14:22:43 -07:00
DESKTOP-GENO133\IvanPlex
8dfee0fd12 added ability to create plans from reminders. 2024-02-18 14:20:20 -07:00
Hargata Softworks
275b60aa14 Merge pull request #310 from hargata/Hargata/supply.usage.history
Supply Usage History
2024-02-18 13:16:46 -07:00
DESKTOP-GENO133\IvanPlex
dd14b71e68 added front end to show used supplies. 2024-02-18 11:32:04 -07:00
DESKTOP-GENO133\IvanPlex
849171c4df moved function to shared js. 2024-02-17 21:43:19 -07:00
DESKTOP-GENO133\IvanPlex
2183c77606 added button to show supply requisition history. 2024-02-17 19:13:09 -07:00
Hargata Softworks
bedf7bad34 Merge pull request #306 from hargata/Hargata/report.usability
Improved Usability
2024-02-17 17:13:41 -07:00
DESKTOP-GENO133\IvanPlex
802c7923d5 added supply usage history 2024-02-17 17:07:31 -07:00
DESKTOP-GENO133\IvanPlex
95c8cd19f8 Updated error message 2024-02-17 10:53:29 -07:00
DESKTOP-GENO133\IvanPlex
e700a5f1c2 functionality to duplicate collaborators across vehicles. 2024-02-17 10:51:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7fd7f7e29d added select all option to reports page. 2024-02-16 15:50:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
902ddd0269 improve usability on reports dropdown 2024-02-16 15:29:06 -07:00
Hargata Softworks
0a6424f8c0 Merge pull request #305 from hargata/Hargata/user.provisioning
revert
2024-02-16 14:07:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3a36c624ba revert 2024-02-16 14:07:22 -07:00
Hargata Softworks
1329beb808 Merge pull request #304 from hargata/Hargata/user.provisioning
attempt to fix docker build.
2024-02-16 14:05:40 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0e8ef0180f attempt to fix docker build. 2024-02-16 14:05:01 -07:00
Hargata Softworks
660f089035 Merge pull request #303 from hargata/Hargata/user.provisioning
Hargata/user.provisioning
2024-02-16 13:57:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
04f90ae6a8 Added Distance Traveled Chart 2024-02-16 13:55:22 -07:00
DESKTOP-T0O5CDB\DESK-555BD
30b4a73fef made datepicker honor locale's week start day 2024-02-16 11:58:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7158f3bf0 automatically log open id user in after they've registered. 2024-02-16 11:06:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f46bbe9963 Allow OpenID Users to sign up with a token. 2024-02-16 10:48:20 -07:00
41 changed files with 658 additions and 86 deletions

View File

@@ -99,6 +99,7 @@ namespace CarCareTracker.Controllers
} else
{
_logger.LogInformation($"User {userEmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
return View("OpenIDRegistration", model: userEmailAddress);
}
}
}
@@ -149,6 +150,27 @@ namespace CarCareTracker.Controllers
return Json(result);
}
[HttpPost]
public IActionResult RegisterOpenIdUser(LoginModel credentials)
{
var result = _loginLogic.RegisterOpenIdUser(credentials);
if (result.Success)
{
var userData = _loginLogic.ValidateOpenIDUser(new LoginModel() { EmailAddress = credentials.EmailAddress });
if (userData.Id != default)
{
AuthCookie authCookie = new AuthCookie
{
UserData = userData,
ExpiresOn = DateTime.Now.AddDays(1)
};
var serializedCookie = JsonSerializer.Serialize(authCookie);
var encryptedCookie = _dataProtector.Protect(serializedCookie);
Response.Cookies.Append("ACCESS_TOKEN", encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
}
}
return Json(result);
}
[HttpPost]
public IActionResult RequestResetPassword(LoginModel credentials)
{
var result = _loginLogic.RequestResetPassword(credentials);

View File

@@ -154,6 +154,34 @@ namespace CarCareTracker.Controllers
_dataAccess.DeleteVehicle(vehicleId);
return Json(result);
}
[HttpPost]
public IActionResult DuplicateVehicleCollaborators(int sourceVehicleId, int destVehicleId)
{
try
{
//retrieve collaborators for both source and destination vehicle id.
if (_userLogic.UserCanEditVehicle(GetUserID(), sourceVehicleId) && _userLogic.UserCanEditVehicle(GetUserID(), destVehicleId))
{
var sourceCollaborators = _userLogic.GetCollaboratorsForVehicle(sourceVehicleId).Select(x => x.UserVehicle.UserId).ToList();
var destCollaborators = _userLogic.GetCollaboratorsForVehicle(destVehicleId).Select(x => x.UserVehicle.UserId).ToList();
sourceCollaborators.RemoveAll(x => destCollaborators.Contains(x));
if (sourceCollaborators.Any()) {
foreach (int collaboratorId in sourceCollaborators)
{
_userLogic.AddUserAccessToVehicle(collaboratorId, destVehicleId);
}
} else
{
return Json(new OperationResponse { Success = false, Message = "Both vehicles already have identical collaborators" });
}
}
return Json(new OperationResponse { Success = true, Message = "Collaborators Copied"});
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
}
}
#region "Bulk Imports and Exports"
[HttpGet]
public IActionResult GetBulkImportModalPartialView(ImportMode mode)
@@ -672,11 +700,11 @@ namespace CarCareTracker.Controllers
}
//move files from temp.
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
if (result && serviceRecord.Supplies.Any())
if (serviceRecord.Supplies.Any())
{
RequisitionSupplyRecordsByUsage(serviceRecord.Supplies);
serviceRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description);
}
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
return Json(result);
}
[HttpGet]
@@ -700,6 +728,7 @@ namespace CarCareTracker.Controllers
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields)
};
return PartialView("_ServiceRecordModal", convertedResult);
@@ -743,11 +772,11 @@ namespace CarCareTracker.Controllers
}
//move files from temp.
collisionRecord.Files = collisionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
if (result && collisionRecord.Supplies.Any())
if (collisionRecord.Supplies.Any())
{
RequisitionSupplyRecordsByUsage(collisionRecord.Supplies);
collisionRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description);
}
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
return Json(result);
}
[HttpGet]
@@ -771,6 +800,7 @@ namespace CarCareTracker.Controllers
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields)
};
return PartialView("_CollisionRecordModal", convertedResult);
@@ -890,6 +920,7 @@ namespace CarCareTracker.Controllers
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
var viewModel = new ReportViewModel();
//get totalCostMakeUp
viewModel.CostMakeUpForVehicle = new CostMakeUpForVehicle
@@ -907,10 +938,12 @@ namespace CarCareTracker.Controllers
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, 0));
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, 0));
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, 0));
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, 0));
viewModel.CostForVehicleByMonth = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost)
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Any(y=>y.MinMileage != default) ? x.Max(y=>y.MaxMileage) - x.Where(y=>y.MinMileage != default).Min(y=>y.MinMileage) : 0
}).ToList();
//get reminders
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now);
@@ -1239,10 +1272,16 @@ namespace CarCareTracker.Controllers
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year));
}
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
{
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year));
}
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost)
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Any(y => y.MinMileage != default) ? x.Max(y => y.MaxMileage) - x.Where(y => y.MinMileage != default).Min(y => y.MinMileage) : 0
}).ToList();
return PartialView("_GasCostByMonthReport", groupedRecord);
}
@@ -1415,11 +1454,11 @@ namespace CarCareTracker.Controllers
}
//move files from temp.
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
if (result && upgradeRecord.Supplies.Any())
if (upgradeRecord.Supplies.Any())
{
RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies);
upgradeRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description);
}
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
return Json(result);
}
[HttpGet]
@@ -1443,6 +1482,7 @@ namespace CarCareTracker.Controllers
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields)
};
return PartialView("_UpgradeRecordModal", convertedResult);
@@ -1515,8 +1555,9 @@ namespace CarCareTracker.Controllers
}
return result;
}
private void RequisitionSupplyRecordsByUsage(List<SupplyUsage> supplyUsage)
private List<SupplyUsageHistory> RequisitionSupplyRecordsByUsage(List<SupplyUsage> supplyUsage, DateTime dateRequisitioned, string usageDescription)
{
List<SupplyUsageHistory> results = new List<SupplyUsageHistory>();
foreach(SupplyUsage supply in supplyUsage)
{
//get supply record.
@@ -1526,9 +1567,29 @@ namespace CarCareTracker.Controllers
result.Quantity -= supply.Quantity;
//deduct cost.
result.Cost -= (supply.Quantity * unitCost);
//check decimal places to ensure that it always has a max of 3 decimal places.
var roundedDecimal = decimal.Round(result.Cost, 3);
if (roundedDecimal != result.Cost)
{
//Too many decimals
result.Cost = roundedDecimal;
}
//create new requisitionrrecord
var requisitionRecord = new SupplyUsageHistory
{
Date = dateRequisitioned,
Description = usageDescription,
Quantity = supply.Quantity,
Cost = (supply.Quantity * unitCost)
};
result.RequisitionHistory.Add(requisitionRecord);
//save
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
requisitionRecord.Description = result.Description; //change the name of the description for plan/service/repair/upgrade records
requisitionRecord.PartNumber = result.PartNumber; //populate part number if not displayed in supplies modal.
results.Add(requisitionRecord);
}
return results;
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
@@ -1598,6 +1659,7 @@ namespace CarCareTracker.Controllers
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields)
};
return PartialView("_SupplyRecordModal", convertedResult);
@@ -1628,11 +1690,11 @@ namespace CarCareTracker.Controllers
planRecord.DateModified = DateTime.Now.ToString("G");
//move files from temp.
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
if (result && planRecord.Supplies.Any())
if (planRecord.Supplies.Any())
{
RequisitionSupplyRecordsByUsage(planRecord.Supplies);
planRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description);
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
return Json(result);
}
[HttpPost]
@@ -1678,15 +1740,24 @@ namespace CarCareTracker.Controllers
return Json(new OperationResponse { Success = false, Message = string.Join("<br>", supplyAvailability) });
}
}
if (existingRecord.ReminderRecordId != default)
{
//check if reminder still exists and is still recurring.
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." });
}
}
//populate createdDate
existingRecord.DateCreated = DateTime.Now.ToString("G");
existingRecord.DateModified = DateTime.Now.ToString("G");
existingRecord.Id = default;
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord.ToPlanRecord());
if (result && existingRecord.Supplies.Any())
if (existingRecord.Supplies.Any())
{
RequisitionSupplyRecordsByUsage(existingRecord.Supplies);
existingRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(existingRecord.Supplies, DateTime.Parse(existingRecord.DateCreated), existingRecord.Description);
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord.ToPlanRecord());
return Json(new OperationResponse { Success = result, Message = result ? "Plan Record Added" : StaticHelper.GenericErrorMessage });
}
[HttpGet]
@@ -1695,6 +1766,16 @@ namespace CarCareTracker.Controllers
return PartialView("_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
}
[HttpPost]
public IActionResult GetAddPlanRecordPartialView(PlanRecordInput? planModel)
{
if (planModel is not null)
{
planModel.ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields;
return PartialView("_PlanRecordModal", planModel);
}
return PartialView("_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
}
[HttpPost]
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0)
{
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
@@ -1726,6 +1807,7 @@ namespace CarCareTracker.Controllers
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(newRecord);
@@ -1741,6 +1823,7 @@ namespace CarCareTracker.Controllers
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(newRecord);
@@ -1756,10 +1839,29 @@ namespace CarCareTracker.Controllers
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(newRecord);
}
//push back any reminders
if (existingRecord.ReminderRecordId != default)
{
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(existingRecord.ReminderRecordId);
if (existingReminder is not null && existingReminder.Id != default && existingReminder.IsRecurring)
{
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder);
//save to db.
var reminderUpdateResult = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);
if (!reminderUpdateResult)
{
_logger.LogError("Unable to update reminder either because the reminder no longer exists or is no longer recurring");
}
} else
{
_logger.LogError("Unable to update reminder because it no longer exists.");
}
}
}
return Json(result);
}
@@ -1781,6 +1883,8 @@ namespace CarCareTracker.Controllers
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
RequisitionHistory = result.RequisitionHistory,
ReminderRecordId = result.ReminderRecordId,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields)
};
return PartialView("_PlanRecordModal", convertedResult);
@@ -1897,5 +2001,6 @@ namespace CarCareTracker.Controllers
return Json(result);
}
#endregion
}
}

View File

@@ -5,6 +5,7 @@ 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);
@@ -13,6 +14,21 @@ namespace CarCareTracker.Helper
}
public class ReportHelper: IReportHelper
{
public IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0)
{
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
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = 0,
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
});
}
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
{
if (year != default)
@@ -23,7 +39,9 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y=>y.Mileage),
MinMileage = x.Min(y=>y.Mileage)
});
}
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
@@ -36,7 +54,9 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
});
}
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
@@ -49,7 +69,9 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
});
}
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
@@ -62,7 +84,9 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
});
}
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)

View File

@@ -16,6 +16,7 @@ namespace CarCareTracker.Logic
OperationResponse GenerateUserToken(string emailAddress, bool autoNotify);
bool DeleteUserToken(int tokenId);
bool DeleteUser(int userId);
OperationResponse RegisterOpenIdUser(LoginModel credentials);
OperationResponse RegisterNewUser(LoginModel credentials);
OperationResponse RequestResetPassword(LoginModel credentials);
OperationResponse ResetPasswordByUser(LoginModel credentials);
@@ -60,6 +61,45 @@ namespace CarCareTracker.Logic
return result.Id != 0;
}
}
public OperationResponse RegisterOpenIdUser(LoginModel credentials)
{
//validate their token.
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
}
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName))
{
return new OperationResponse { Success = false, Message = "Username cannot be blank" };
}
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUser.Id != default)
{
return new OperationResponse { Success = false, Message = "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" };
}
_tokenData.DeleteToken(existingToken.Id);
var newUser = new UserData()
{
UserName = credentials.UserName,
Password = GetHash(NewToken()), //generate a password for OpenID User
EmailAddress = credentials.EmailAddress
};
var result = _userData.SaveUserRecord(newUser);
if (result)
{
return new OperationResponse { Success = true, Message = "You will be logged in briefly." };
}
else
{
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
}
}
//handles user registration
public OperationResponse RegisterNewUser(LoginModel credentials)
{

View File

@@ -13,6 +13,20 @@
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public CollisionRecord ToCollisionRecord() { return new CollisionRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public CollisionRecord ToCollisionRecord() { return new CollisionRecord {
Id = Id,
VehicleId = VehicleId,
Date = DateTime.Parse(Date),
Cost = Cost,
Mileage = Mileage,
Description = Description,
Notes = Notes,
Files = Files,
Tags = Tags,
ExtraFields = ExtraFields,
RequisitionHistory = RequisitionHistory
};
}
}
}

View File

@@ -12,5 +12,6 @@
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
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>();
}
}

View File

@@ -1,8 +0,0 @@
namespace CarCareTracker.Models
{
public class PlanCostItem
{
public string CostName { get; set; }
public decimal CostAmount { get; set; }
}
}

View File

@@ -4,6 +4,7 @@
{
public int Id { get; set; }
public int VehicleId { get; set; }
public int ReminderRecordId { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public string Description { get; set; }
@@ -14,5 +15,6 @@
public PlanProgress Progress { get; set; }
public decimal Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
}
}

View File

@@ -4,6 +4,7 @@
{
public int Id { get; set; }
public int VehicleId { get; set; }
public int ReminderRecordId { get; set; }
public string DateCreated { get; set; } = DateTime.Now.ToShortDateString();
public string DateModified { get; set; } = DateTime.Now.ToShortDateString();
public string Description { get; set; }
@@ -15,9 +16,11 @@
public PlanProgress Progress { get; set; }
public decimal Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public PlanRecord ToPlanRecord() { return new PlanRecord {
Id = Id,
VehicleId = VehicleId,
ReminderRecordId = ReminderRecordId,
DateCreated = DateTime.Parse(DateCreated),
DateModified = DateTime.Parse(DateModified),
Description = Description,
@@ -27,7 +30,12 @@
Cost = Cost,
Priority = Priority,
Progress = Progress,
ExtraFields = ExtraFields
ExtraFields = ExtraFields,
RequisitionHistory = RequisitionHistory
}; }
/// <summary>
/// only used to hide view template button on plan create modal.
/// </summary>
public bool CreatedFromReminder { get; set; }
}
}

View File

@@ -5,5 +5,8 @@
public int MonthId { get; set; }
public string MonthName { get; set; }
public decimal Cost { get; set; }
public int MaxMileage { get; set; }
public int MinMileage { get; set; }
public int DistanceTraveled { get; set; }
}
}

View File

@@ -13,6 +13,20 @@
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public ServiceRecord ToServiceRecord() { return new ServiceRecord {
Id = Id,
VehicleId = VehicleId,
Date = DateTime.Parse(Date),
Cost = Cost,
Mileage = Mileage,
Description = Description,
Notes = Notes,
Files = Files,
Tags = Tags,
ExtraFields = ExtraFields,
RequisitionHistory = RequisitionHistory
};
}
}
}

View File

@@ -35,5 +35,6 @@
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
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>();
}
}

View File

@@ -14,6 +14,7 @@
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
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 SupplyRecord ToSupplyRecord() { return new SupplyRecord {
Id = Id,
VehicleId = VehicleId,
@@ -26,7 +27,8 @@
Notes = Notes,
Files = Files,
Tags = Tags,
ExtraFields = ExtraFields
ExtraFields = ExtraFields,
RequisitionHistory = RequisitionHistory
}; }
}
}

View File

@@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public class SupplyUsageHistory {
public DateTime Date { get; set; }
public string PartNumber { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
public decimal Cost { get; set; }
}
}

View File

@@ -13,6 +13,20 @@
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord {
Id = Id,
VehicleId = VehicleId,
Date = DateTime.Parse(Date),
Cost = Cost,
Mileage = Mileage,
Description = Description,
Notes = Notes,
Files = Files,
Tags = Tags,
ExtraFields = ExtraFields,
RequisitionHistory = RequisitionHistory
};
}
}
}

View File

@@ -23,9 +23,9 @@
<div class="row gy-3 align-items-stretch vehiclesContainer">
@foreach (Vehicle vehicle in Model)
{
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none garage-item" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
<div class="card" onclick="viewVehicle(@vehicle.Id)">
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down;" />
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none;" />
<div class="card-body">
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>
<h5 class="card-title text-truncate">@($"{vehicle.Make}")</h5>

View File

@@ -201,7 +201,7 @@
<img src="/defaults/lubelogger_logo.png" />
</div>
<div class="d-flex justify-content-center">
<small class="text-body-secondary">Version 1.1.9</small>
<small class="text-body-secondary">Version 1.2.1</small>
</div>
<p class="lead">
Proudly developed in the rural town of Price, Utah by Hargata Softworks.

View File

@@ -0,0 +1,50 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var logoUrl = config.GetLogoUrl();
var userLanguage = config.GetServerLanguage();
}
@model string
@{
ViewData["Title"] = "LubeLogger - Register";
}
@section Scripts {
<script src="~/js/login.js"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">
<div class="col-12">
<img src="@logoUrl" />
<div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
<input type="text" id="inputToken" class="form-control">
</div>
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
<input type="text" id="inputUserName" class="form-control" value="@Model">
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performOpenIdRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Register")</button>
</div>
<div class="d-grid">
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
</div>
</div>
</div>
</div>
<script>
function performOpenIdRegistration() {
var token = $("#inputToken").val();
var userName = $("#inputUserName").val();
var userEmail = decodeHTMLEntities('@Model');
$.post('/Login/RegisterOpenIdUser', { userName: userName, token: token, emailAddress: userEmail }, function (data) {
if (data.success) {
successToast(data.message);
setTimeout(function () { window.location.href = '/Home' }, 500);
} else {
errorToast(data.message);
}
});
}
</script>

View File

@@ -10,6 +10,7 @@
var useMarkDown = userConfig.UseMarkDownOnSavedNotes;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var shortDatePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
var firstDayOfWeek = (int)System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
var numberFormat = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
var userLanguage = userConfig.UserLanguage;
shortDatePattern = shortDatePattern.ToLower();
@@ -58,7 +59,8 @@
useMarkDown: "@useMarkDown" == "True",
currencySymbol: decodeHTMLEntities("@numberFormat.CurrencySymbol"),
useThreeDecimals: "@useThreeDecimals" == "True",
useMPG: "@useMPG" == "True"
useMPG: "@useMPG" == "True",
firstDayOfWeek: @firstDayOfWeek
}
}
function getShortDatePattern() {

View File

@@ -83,6 +83,10 @@
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteCollisionRecord(@Model.Id)">@translator.Translate(userLanguage,"Delete")</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
@@ -105,6 +109,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)
<script>
var uploadedFiles = [];
var selectedSupplies = [];
@@ -114,7 +119,7 @@
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
}
function getCollisionRecordModelData() {
return { id: @Model.Id}
}

View File

@@ -8,7 +8,7 @@
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
var sortedByMPG = Model.OrderBy(x => x.Cost).ToList();
}
@if (Model.Where(x=>x.Cost > 0).Any())
@if (Model.Where(x=>x.Cost > 0).Any() || Model.Where(x=>x.DistanceTraveled > 0).Any())
{
<canvas id="bar-chart"></canvas>
<script>
@@ -16,6 +16,7 @@
function renderChart() {
var barGraphLabels = [];
var barGraphData = [];
var lineChartData = [];
//color gradient from high to low
var barGraphColors = [];
var useDarkMode = getGlobalConfig().useDarkMode;
@@ -23,6 +24,7 @@
{
@:barGraphLabels.push(decodeHTMLEntities("@gasCost.MonthName"));
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
@:lineChartData.push(globalParseFloat('@gasCost.DistanceTraveled'));
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
@:barGraphColors.push('@barGraphColors[index]');
}
@@ -34,7 +36,18 @@
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Month")'),
backgroundColor: barGraphColors,
data: barGraphData
data: barGraphData,
order: 1,
yAxisID: 'y',
},
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Distance Traveled by Month")'),
data: lineChartData,
borderColor: useDarkMode ? "#fff" : "#000",
backgroundColor: useDarkMode ? "#000" : "#fff",
type: 'line',
order: 0,
yAxisID: 'y1',
}
]
},
@@ -43,7 +56,7 @@
title: {
display: true,
color: useDarkMode ? "#fff" : "#000",
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Month")')
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses and Distance Traveled by Month")')
},
legend: {
display: false,
@@ -59,6 +72,13 @@
color: useDarkMode ? "#fff" : "#000"
}
},
y1: {
beginAtZero: true,
position: 'right',
ticks: {
color: useDarkMode ? "#fff" : "#000"
}
},
x: {
ticks: {
color: useDarkMode ? "#fff" : "#000"

View File

@@ -16,7 +16,13 @@
</div>
</div>
<div class="row">
<div class="col-6 col-md-1">
@if (Model.ReminderRecordId != default)
{
<div class="col-4 col-md-1">
<i class="bi bi-bell"></i>
</div>
}
<div class="@(Model.ReminderRecordId != default ? "col-4" : "col-6") col-md-1">
@if (Model.ImportMode == ImportMode.ServiceRecord)
{
<i class="bi bi-card-checklist"></i>
@@ -30,7 +36,7 @@
<i class="bi bi-exclamation-octagon"></i>
}
</div>
<div class="col-6 col-md-1">
<div class="@(Model.ReminderRecordId != default ? "col-4" : "col-6") col-md-1">
@if (Model.Priority == PlanPriority.Critical)
{
<i class="bi bi-fire"></i>

View File

@@ -85,6 +85,10 @@
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<button type="button" class="btn btn-danger" onclick="deletePlanRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
@@ -97,7 +101,10 @@
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="savePlanRecordTemplate()">@translator.Translate(userLanguage, "Save as Template")</a></li>
<li><a class="dropdown-item" href="#" onclick="showPlanRecordTemplatesModal()">@translator.Translate(userLanguage, "View Templates")</a></li>
@if (!Model.CreatedFromReminder)
{
<li><a class="dropdown-item" href="#" onclick="showPlanRecordTemplatesModal()">@translator.Translate(userLanguage, "View Templates")</a></li>
}
</ul>
</div>
}
@@ -106,6 +113,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)
<script>
var uploadedFiles = [];
var selectedSupplies = [];
@@ -119,7 +127,9 @@
function getPlanRecordModelData() {
return {
id: @Model.Id,
dateCreated: decodeHTMLEntities('@(Model.DateCreated)')
dateCreated: decodeHTMLEntities('@(Model.DateCreated)'),
reminderRecordId: decodeHTMLEntities('@Model.ReminderRecordId'),
createdFromReminder: @Model.CreatedFromReminder.ToString().ToLower()
}
}
</script>

View File

@@ -29,6 +29,10 @@
<tr class="d-flex" id="supplyRows">
<td class="col-8 text-truncate">
@StaticHelper.TruncateStrings(planRecordTemplate.Description)
@if(planRecordTemplate.ReminderRecordId != default)
{
<i class="bi bi-bell ms-2"></i>
}
@if (planRecordTemplate.Files.Any())
{
<i class="bi bi-paperclip ms-2"></i>

View File

@@ -90,6 +90,10 @@
<div class="modal-footer">
@if (!isNew)
{
@if (Model.IsRecurring)
{
<button type="button" class="btn btn-warning" onclick="createPlanRecordFromReminder(@Model.Id)">@translator.Translate(userLanguage, "Plan")</button>
}
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddReminderRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>

View File

@@ -94,3 +94,9 @@
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="planRecordModal" tabindex="-1" role="dialog" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="planRecordModalContent">
</div>
</div>
</div>

View File

@@ -29,26 +29,55 @@
<div class="col-md-6 col-12 mt-2">
<div class="row">
<div class="col-md-1 d-sm-none d-md-block"></div>
<div class="col-12 col-md-10 reportsCheckBoxContainer">
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="serviceExpenseCheck" value="1" checked>
<label class="form-check-label" for="serviceExpenseCheck">@translator.Translate(userLanguage,"Service")</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="repairExpenseCheck" value="2" checked>
<label class="form-check-label" for="repairExpenseCheck">@translator.Translate(userLanguage, "Repairs")</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="upgradeExpenseCheck" value="3" checked>
<label class="form-check-label" for="upgradeExpenseCheck">@translator.Translate(userLanguage, "Upgrades")</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="gasExpenseCheck" value="4" checked>
<label class="form-check-label" for="gasExpenseCheck">@translator.Translate(userLanguage, "Fuel")</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="taxExpenseCheck" value="5" checked>
<label class="form-check-label" for="taxExpenseCheck">@translator.Translate(userLanguage, "Taxes")</label>
<div class="col-12 col-md-10">
<div class="dropdown d-grid dropdown-center">
<button class="btn btn-outline-warning dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
Metrics
</button>
<ul class="dropdown-menu" style="width:100%;">
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input" onChange="updateCheckAll()" type="checkbox" id="selectAllExpenseCheck" value="0" checked>
<label class="form-check-label stretched-link" for="selectAllExpenseCheck">@translator.Translate(userLanguage, "Select All")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="odometerExpenseCheck" value="6" checked>
<label class="form-check-label stretched-link" for="odometerExpenseCheck">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="serviceExpenseCheck" value="1" checked>
<label class="form-check-label stretched-link" for="serviceExpenseCheck">@translator.Translate(userLanguage, "Service")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="repairExpenseCheck" value="2" checked>
<label class="form-check-label stretched-link" for="repairExpenseCheck">@translator.Translate(userLanguage, "Repairs")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="upgradeExpenseCheck" value="3" checked>
<label class="form-check-label stretched-link" for="upgradeExpenseCheck">@translator.Translate(userLanguage, "Upgrades")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="gasExpenseCheck" value="4" checked>
<label class="form-check-label stretched-link" for="gasExpenseCheck">@translator.Translate(userLanguage, "Fuel")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="taxExpenseCheck" value="5" checked>
<label class="form-check-label stretched-link" for="taxExpenseCheck">@translator.Translate(userLanguage, "Taxes")</label>
</div>
</li>
</ul>
</div>
</div>
<div class="col-md-1 d-sm-none d-md-block"></div>

View File

@@ -83,6 +83,10 @@
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteServiceRecord(@Model.Id)">@translator.Translate(userLanguage,"Delete")</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
@@ -105,6 +109,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)
<script>
var uploadedFiles = [];
var selectedSupplies = [];

View File

@@ -80,6 +80,10 @@
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-clock-history"></i></button>
}
<button type="button" class="btn btn-danger" onclick="deleteSupplyRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddSupplyRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
@@ -92,15 +96,16 @@
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Supply Record")</button>
}
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
<script>
var uploadedFiles = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
function getSupplyRecordModelData() {
return { id: @Model.Id}
}

View File

@@ -0,0 +1,72 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<SupplyUsageHistory>
<script>
var supplyUsageHistory = [];
</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())
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
<table class="table table-hover">
<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))){
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Description")</th>
} else
{
<th scope="col" class="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>
</tr>
</thead>
<tbody>
@foreach (SupplyUsageHistory usageHistory in Model)
{
<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")") })
</script>
<tr class="d-flex">
<td class="col-2">@StaticHelper.TruncateStrings(usageHistory.Date.ToShortDateString())</td>
@if (!string.IsNullOrWhiteSpace(usageHistory.PartNumber))
{
<td class="col-2 text-truncate">@StaticHelper.TruncateStrings(usageHistory.PartNumber)</td>
<td class="col-4 text-truncate">@StaticHelper.TruncateStrings(usageHistory.Description)</td>
} else
{
<td class="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>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="row">
<div class="col-12">
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No supply requisitions in history")</h4>
</div>
</div>
</div>
}
</div>
</div>

View File

@@ -83,6 +83,10 @@
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteUpgradeRecord(@Model.Id)">@translator.Translate(userLanguage,"Delete")</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
@@ -105,6 +109,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)
<script>
var uploadedFiles = [];
var selectedSupplies = [];

File diff suppressed because one or more lines are too long

View File

@@ -129,6 +129,7 @@ function getAndValidateCollisionRecordValues() {
supplies: selectedSupplies,
tags: collisionTags,
addReminderRecord: addReminderRecord,
extraFields: extraFields.extraFields
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory
}
}

View File

@@ -149,6 +149,7 @@ function initCalendar() {
startDate: "+0d",
format: getShortDatePattern().pattern,
todayHighlight: true,
weekStart: getGlobalConfig().firstDayOfWeek,
beforeShowDay: function (date) {
var reminderDateIndex = groupedDates.findIndex(x => (x.date == date.getTime() || x.date == (date.getTime() - date.getTimezoneOffset() * 60000))); //take into account server timezone offset
if (reminderDateIndex > -1) {
@@ -306,3 +307,47 @@ function sortGarage(sender, isMobile) {
}
}
}
let dragged = null;
let draggedId = 0;
function dragEnter(event) {
event.preventDefault();
}
function dragStart(event, vehicleId) {
dragged = event.target;
draggedId = vehicleId;
event.dataTransfer.setData('text/plain', draggedId);
}
function dragOver(event) {
event.preventDefault();
}
function dropBox(event, targetVehicleId) {
if (dragged.parentElement != event.target && event.target != dragged && draggedId != targetVehicleId) {
copyContributors(draggedId, targetVehicleId);
}
event.preventDefault();
}
function copyContributors(sourceVehicleId, destVehicleId) {
var sourceVehicleName = $(`#gridVehicle_${sourceVehicleId} .card-body`).children('h5').map((index, elem) => { return elem.innerText }).toArray().join(" ");
var destVehicleName = $(`#gridVehicle_${destVehicleId} .card-body`).children('h5').map((index, elem) => { return elem.innerText }).toArray().join(" ");
Swal.fire({
title: "Copy Collaborators?",
text: `Copy collaborators over from ${sourceVehicleName} to ${destVehicleName}?`,
showCancelButton: true,
confirmButtonText: "Copy",
confirmButtonColor: "#0d6efd"
}).then((result) => {
if (result.isConfirmed) {
$.post('/Vehicle/DuplicateVehicleCollaborators', { sourceVehicleId: sourceVehicleId, destVehicleId: destVehicleId }, function (data) {
if (data.success) {
successToast("Collaborators Copied");
loadGarage();
} else {
errorToast(data.message);
}
})
} else {
$("#workAroundInput").hide();
}
});
}

View File

@@ -25,6 +25,10 @@ function showEditPlanRecordModal(planRecordId) {
}
function hideAddPlanRecordModal() {
$('#planRecordModal').modal('hide');
if (getPlanRecordModelData().createdFromReminder) {
//show reminder Modal
$("#reminderRecordModal").modal("show");
}
}
function deletePlanRecord(planRecordId) {
$("#workAroundInput").show();
@@ -64,10 +68,12 @@ function savePlanRecordToVehicle(isEdit) {
if (data) {
successToast(isEdit ? "Plan Record Updated" : "Plan Record Added.");
hideAddPlanRecordModal();
saveScrollPosition();
getVehiclePlanRecords(formValues.vehicleId);
if (formValues.addReminderRecord) {
setTimeout(function () { showAddReminderModal(formValues); }, 500);
if (!getPlanRecordModelData().createdFromReminder) {
saveScrollPosition();
getVehiclePlanRecords(formValues.vehicleId);
if (formValues.addReminderRecord) {
setTimeout(function () { showAddReminderModal(formValues); }, 500);
}
}
} else {
errorToast(genericErrorMessage());
@@ -139,9 +145,6 @@ function savePlanRecordTemplate() {
$.post('/Vehicle/SavePlanRecordTemplateToVehicleId', { planRecord: formValues }, function (data) {
if (data.success) {
successToast(data.message);
hideAddPlanRecordModal();
saveScrollPosition();
getVehiclePlanRecords(formValues.vehicleId);
} else {
errorToast(data.message);
}
@@ -157,6 +160,7 @@ function getAndValidatePlanRecordValues() {
var planDateCreated = getPlanRecordModelData().dateCreated;
var vehicleId = GetVehicleId().vehicleId;
var planRecordId = getPlanRecordModelData().id;
var reminderRecordId = getPlanRecordModelData().reminderRecordId;
//validation
var hasError = false;
var extraFields = getAndValidateExtraFields();
@@ -188,7 +192,9 @@ function getAndValidatePlanRecordValues() {
priority: planPriority,
progress: planProgress,
importMode: planType,
extraFields: extraFields.extraFields
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory,
reminderRecordId: reminderRecordId
}
}
//drag and drop stuff.

View File

@@ -218,3 +218,25 @@ function getAndValidateReminderRecordValues() {
customMonthInterval: customMonthInterval
}
}
function createPlanRecordFromReminder(reminderRecordId) {
//get values
var formValues = getAndValidateReminderRecordValues();
//validate
if (formValues.hasError) {
errorToast("Please check the form data");
return;
}
var planModelInput = {
id: 0,
createdFromReminder: true,
vehicleId: formValues.vehicleId,
reminderRecordId: reminderRecordId,
description: formValues.description,
notes: formValues.notes
};
$.post('/Vehicle/GetAddPlanRecordPartialView', { planModel: planModelInput }, function (data) {
$("#reminderRecordModal").modal("hide");
$("#planRecordModalContent").html(data);
$("#planRecordModal").modal("show");
});
}

View File

@@ -12,8 +12,15 @@ function generateVehicleHistoryReport() {
}
})
}
function updateCheckAll() {
var isChecked = $("#selectAllExpenseCheck").is(":checked");
$(".reportCheckBox").prop('checked', isChecked);
setDebounce(refreshBarChart);
}
function updateCheck() {
setDebounce(refreshBarChart);
var allIsChecked = $(".reportCheckBox:checked").length == 6;
$("#selectAllExpenseCheck").prop("checked", allIsChecked);
}
function refreshMPGChart() {
var vehicleId = GetVehicleId().vehicleId;
@@ -42,6 +49,9 @@ function refreshBarChart() {
if ($("#taxExpenseCheck").is(":checked")) {
selectedMetrics.push('TaxRecord');
}
if ($("#odometerExpenseCheck").is(":checked")) {
selectedMetrics.push('OdometerRecord');
}
$.post('/Vehicle/GetCostByMonthByVehicle',
{

View File

@@ -129,6 +129,7 @@ function getAndValidateServiceRecordValues() {
supplies: selectedSupplies,
tags: serviceTags,
addReminderRecord: addReminderRecord,
extraFields: extraFields.extraFields
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory
}
}

View File

@@ -129,13 +129,15 @@ function initDatePicker(input, futureOnly) {
input.datepicker({
startDate: "+0d",
format: getShortDatePattern().pattern,
autoclose: true
autoclose: true,
weekStart: getGlobalConfig().firstDayOfWeek
});
} else {
input.datepicker({
endDate: "+0d",
format: getShortDatePattern().pattern,
autoclose: true
autoclose: true,
weekStart: getGlobalConfig().firstDayOfWeek
});
}
}
@@ -413,3 +415,11 @@ function getAndValidateExtraFields() {
});
return { hasError: hasError, extraFields: outputData };
}
function toggleSupplyUsageHistory() {
var container = $("#supplyUsageHistoryModalContainer");
if (container.hasClass("d-none")) {
container.removeClass("d-none");
} else {
container.addClass("d-none");
}
}

View File

@@ -130,6 +130,7 @@ function getAndValidateSupplyRecordValues() {
quantity: supplyQuantity,
files: uploadedFiles,
tags: supplyTags,
extraFields: extraFields.extraFields
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory
}
}

View File

@@ -129,6 +129,7 @@ function getAndValidateUpgradeRecordValues() {
supplies: selectedSupplies,
tags: upgradeTags,
addReminderRecord: addReminderRecord,
extraFields: extraFields.extraFields
extraFields: extraFields.extraFields,
requisitionHistory: supplyUsageHistory
}
}