diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index 5ca76c0..80f05f3 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -1,4 +1,5 @@ -using CarCareTracker.External.Interfaces; +using CarCareTracker.External.Implementations; +using CarCareTracker.External.Interfaces; using CarCareTracker.Filter; using CarCareTracker.Helper; using CarCareTracker.Logic; @@ -20,6 +21,7 @@ namespace CarCareTracker.Controllers private readonly ITaxRecordDataAccess _taxRecordDataAccess; private readonly IReminderRecordDataAccess _reminderRecordDataAccess; private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess; + private readonly IOdometerRecordDataAccess _odometerRecordDataAccess; private readonly IReminderHelper _reminderHelper; private readonly IGasHelper _gasHelper; private readonly IUserLogic _userLogic; @@ -34,6 +36,7 @@ namespace CarCareTracker.Controllers ITaxRecordDataAccess taxRecordDataAccess, IReminderRecordDataAccess reminderRecordDataAccess, IUpgradeRecordDataAccess upgradeRecordDataAccess, + IOdometerRecordDataAccess odometerRecordDataAccess, IFileHelper fileHelper, IUserLogic userLogic) { @@ -45,6 +48,7 @@ namespace CarCareTracker.Controllers _taxRecordDataAccess = taxRecordDataAccess; _reminderRecordDataAccess = reminderRecordDataAccess; _upgradeRecordDataAccess = upgradeRecordDataAccess; + _odometerRecordDataAccess = odometerRecordDataAccess; _gasHelper = gasHelper; _reminderHelper = reminderHelper; _userLogic = userLogic; @@ -106,6 +110,47 @@ namespace CarCareTracker.Controllers } [TypeFilter(typeof(CollaboratorFilter))] [HttpGet] + [Route("/api/vehicle/odometerrecords")] + public IActionResult OdometerRecords(int vehicleId) + { + var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId); + var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), Odometer = x.Mileage.ToString(), Notes = x.Notes }); + return Json(result); + } + [TypeFilter(typeof(CollaboratorFilter))] + [HttpPost] + [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"; + return Json(response); + } + try + { + var odometerRecord = new OdometerRecord() + { + VehicleId = vehicleId, + Date = DateTime.Parse(input.Date), + Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, + Mileage = int.Parse(input.Odometer) + }; + _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord); + response.Success = true; + response.Message = "Odometer Record Added"; + return Json(response); + } catch (Exception ex) + { + response.Success = false; + response.Message = StaticHelper.GenericErrorMessage; + return Json(response); + } + } + [TypeFilter(typeof(CollaboratorFilter))] + [HttpGet] [Route("/api/vehicle/gasrecords")] public IActionResult GasRecords(int vehicleId, bool useMPG, bool useUKMPG) { @@ -164,6 +209,11 @@ namespace CarCareTracker.Controllers { numbersArray.Add(upgradeRecords.Max(x => x.Mileage)); } + var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId); + if (odometerRecords.Any()) + { + numbersArray.Add(odometerRecords.Max(x => x.Mileage)); + } return numbersArray.Any() ? numbersArray.Max() : 0; } } diff --git a/Controllers/VehicleController.cs b/Controllers/VehicleController.cs index 5f4c24c..0352d11 100644 --- a/Controllers/VehicleController.cs +++ b/Controllers/VehicleController.cs @@ -26,6 +26,8 @@ namespace CarCareTracker.Controllers private readonly IReminderRecordDataAccess _reminderRecordDataAccess; private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess; private readonly ISupplyRecordDataAccess _supplyRecordDataAccess; + private readonly IPlanRecordDataAccess _planRecordDataAccess; + private readonly IOdometerRecordDataAccess _odometerRecordDataAccess; private readonly IWebHostEnvironment _webEnv; private readonly IConfigHelper _config; private readonly IFileHelper _fileHelper; @@ -48,6 +50,8 @@ namespace CarCareTracker.Controllers IReminderRecordDataAccess reminderRecordDataAccess, IUpgradeRecordDataAccess upgradeRecordDataAccess, ISupplyRecordDataAccess supplyRecordDataAccess, + IPlanRecordDataAccess planRecordDataAccess, + IOdometerRecordDataAccess odometerRecordDataAccess, IUserLogic userLogic, IWebHostEnvironment webEnv, IConfigHelper config) @@ -66,6 +70,8 @@ namespace CarCareTracker.Controllers _reminderRecordDataAccess = reminderRecordDataAccess; _upgradeRecordDataAccess = upgradeRecordDataAccess; _supplyRecordDataAccess = supplyRecordDataAccess; + _planRecordDataAccess = planRecordDataAccess; + _odometerRecordDataAccess = odometerRecordDataAccess; _userLogic = userLogic; _webEnv = webEnv; _config = config; @@ -134,12 +140,13 @@ namespace CarCareTracker.Controllers _noteDataAccess.DeleteAllNotesByVehicleId(vehicleId) && _reminderRecordDataAccess.DeleteAllReminderRecordsByVehicleId(vehicleId) && _upgradeRecordDataAccess.DeleteAllUpgradeRecordsByVehicleId(vehicleId) && + _planRecordDataAccess.DeleteAllPlanRecordsByVehicleId(vehicleId) && _supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) && _userLogic.DeleteAllAccessToVehicle(vehicleId) && _dataAccess.DeleteVehicle(vehicleId); return Json(result); } - #region "Bulk Imports" + #region "Bulk Imports and Exports" [HttpGet] public IActionResult GetBulkImportModalPartialView(ImportMode mode) { @@ -211,6 +218,24 @@ namespace CarCareTracker.Controllers return Json($"/{fileNameToExport}"); } } + else if (mode == ImportMode.OdometerRecord) + { + var fileNameToExport = $"temp/{Guid.NewGuid()}.csv"; + var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false); + var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId); + if (vehicleRecords.Any()) + { + var exportData = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), Notes = x.Notes, Odometer = x.Mileage.ToString() }); + using (var writer = new StreamWriter(fullExportFilePath)) + { + using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + { + csv.WriteRecords(exportData); + } + } + return Json($"/{fileNameToExport}"); + } + } else if (mode == ImportMode.SupplyRecord) { var fileNameToExport = $"temp/{Guid.NewGuid()}.csv"; @@ -218,14 +243,16 @@ namespace CarCareTracker.Controllers var vehicleRecords = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId); if (vehicleRecords.Any()) { - var exportData = vehicleRecords.Select(x => new SupplyRecordExportModel { - Date = x.Date.ToShortDateString(), - Description = x.Description, - Cost = x.Cost.ToString("C"), + var exportData = vehicleRecords.Select(x => new SupplyRecordExportModel + { + Date = x.Date.ToShortDateString(), + Description = x.Description, + Cost = x.Cost.ToString("C"), PartNumber = x.PartNumber, PartQuantity = x.Quantity.ToString(), PartSupplier = x.PartSupplier, - Notes = x.Notes }); + Notes = x.Notes + }); using (var writer = new StreamWriter(fullExportFilePath)) { using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) @@ -254,6 +281,34 @@ namespace CarCareTracker.Controllers return Json($"/{fileNameToExport}"); } } + else if (mode == ImportMode.PlanRecord) + { + var fileNameToExport = $"temp/{Guid.NewGuid()}.csv"; + var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false); + var vehicleRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId); + if (vehicleRecords.Any()) + { + var exportData = vehicleRecords.Select(x => new PlanRecordExportModel + { + DateCreated = x.DateCreated.ToString("G"), + DateModified = x.DateModified.ToString("G"), + Description = x.Description, + Cost = x.Cost.ToString("C"), + Type = x.ImportMode.ToString(), + Priority = x.Priority.ToString(), + Progress = x.Progress.ToString(), + Notes = x.Notes + }); + using (var writer = new StreamWriter(fullExportFilePath)) + { + using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + { + csv.WriteRecords(exportData); + } + } + return Json($"/{fileNameToExport}"); + } + } else if (mode == ImportMode.GasRecord) { var fileNameToExport = $"temp/{Guid.NewGuid()}.csv"; @@ -372,6 +427,36 @@ namespace CarCareTracker.Controllers }; _serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord); } + else if (mode == ImportMode.OdometerRecord) + { + var convertedRecord = new OdometerRecord() + { + VehicleId = vehicleId, + Date = DateTime.Parse(importModel.Date), + Mileage = int.Parse(importModel.Odometer, NumberStyles.Any), + Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes + }; + _odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord); + } + else if (mode == ImportMode.PlanRecord) + { + var progressIsEnum = Enum.TryParse(importModel.Progress, out PlanProgress parsedProgress); + var typeIsEnum = Enum.TryParse(importModel.Type, out ImportMode parsedType); + var priorityIsEnum = Enum.TryParse(importModel.Priority, out PlanPriority parsedPriority); + var convertedRecord = new PlanRecord() + { + VehicleId = vehicleId, + DateCreated = DateTime.Parse(importModel.DateCreated), + DateModified = DateTime.Parse(importModel.DateModified), + Progress = parsedProgress, + ImportMode = parsedType, + Priority = parsedPriority, + Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Plan Record on {importModel.DateCreated}" : importModel.Description, + Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes, + Cost = decimal.Parse(importModel.Cost, NumberStyles.Any) + }; + _planRecordDataAccess.SavePlanRecordToVehicle(convertedRecord); + } else if (mode == ImportMode.RepairRecord) { var convertedRecord = new CollisionRecord() @@ -959,6 +1044,11 @@ namespace CarCareTracker.Controllers { numbersArray.Add(upgradeRecords.Max(x => x.Mileage)); } + var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId); + if (odometerRecords.Any()) + { + numbersArray.Add(odometerRecords.Max(x => x.Mileage)); + } return numbersArray.Any() ? numbersArray.Max() : 0; } private List GetRemindersAndUrgency(int vehicleId, DateTime dateCompare) @@ -973,7 +1063,37 @@ namespace CarCareTracker.Controllers public IActionResult GetVehicleHaveUrgentOrPastDueReminders(int vehicleId) { var result = GetRemindersAndUrgency(vehicleId, DateTime.Now); - if (result.Where(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue).Any()) + //check for past due reminders that are eligible for recurring. + var pastDueAndRecurring = result.Where(x => x.Urgency == ReminderUrgency.PastDue && x.IsRecurring); + if (pastDueAndRecurring.Any()) + { + foreach (ReminderRecordViewModel reminderRecord in pastDueAndRecurring) + { + //update based on recurring intervals. + //pull reminderRecord based on ID + var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecord.Id); + if (existingReminder.Metric == ReminderMetric.Both) + { + existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval); + existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval; + } + else if (existingReminder.Metric == ReminderMetric.Odometer) + { + existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval; + } + else if (existingReminder.Metric == ReminderMetric.Date) + { + existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval); + } + //save to db. + _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder); + //set urgency to not urgent so it gets excluded in count. + reminderRecord.Urgency = ReminderUrgency.NotUrgent; + } + } + //check for very urgent or past due reminders that were not eligible for recurring. + var pastDueAndUrgentReminders = result.Where(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue); + if (pastDueAndUrgentReminders.Any()) { return Json(true); } @@ -1018,7 +1138,10 @@ namespace CarCareTracker.Controllers Notes = result.Notes, VehicleId = result.VehicleId, Mileage = result.Mileage, - Metric = result.Metric + Metric = result.Metric, + IsRecurring = result.IsRecurring, + ReminderMileageInterval = result.ReminderMileageInterval, + ReminderMonthInterval = result.ReminderMonthInterval }; return PartialView("_ReminderRecordModal", convertedResult); } @@ -1173,5 +1296,168 @@ namespace CarCareTracker.Controllers return Json(result); } #endregion + #region "Plan Records" + [TypeFilter(typeof(CollaboratorFilter))] + [HttpGet] + public IActionResult GetPlanRecordsByVehicleId(int vehicleId) + { + var result = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId); + return PartialView("_PlanRecords", result); + } + [HttpPost] + public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord) + { + //populate createdDate + if (planRecord.Id == default) + { + planRecord.DateCreated = DateTime.Now.ToString("G"); + } + 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()); + return Json(result); + } + [HttpGet] + public IActionResult GetAddPlanRecordPartialView() + { + return PartialView("_PlanRecordModal", new PlanRecordInput()); + } + [HttpPost] + public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0) + { + var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId); + existingRecord.Progress = planProgress; + existingRecord.DateModified = DateTime.Now; + var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord); + if (planProgress == PlanProgress.Done) + { + //convert plan record to service/upgrade/repair record. + if (existingRecord.ImportMode == ImportMode.ServiceRecord) + { + var newRecord = new ServiceRecord() + { + VehicleId = existingRecord.VehicleId, + Date = DateTime.Now, + Mileage = odometer, + Description = existingRecord.Description, + Cost = existingRecord.Cost, + Notes = existingRecord.Notes, + Files = existingRecord.Files + }; + _serviceRecordDataAccess.SaveServiceRecordToVehicle(newRecord); + } + else if (existingRecord.ImportMode == ImportMode.RepairRecord) + { + var newRecord = new CollisionRecord() + { + VehicleId = existingRecord.VehicleId, + Date = DateTime.Now, + Mileage = odometer, + Description = existingRecord.Description, + Cost = existingRecord.Cost, + Notes = existingRecord.Notes, + Files = existingRecord.Files + }; + _collisionRecordDataAccess.SaveCollisionRecordToVehicle(newRecord); + } + else if (existingRecord.ImportMode == ImportMode.UpgradeRecord) + { + var newRecord = new UpgradeRecord() + { + VehicleId = existingRecord.VehicleId, + Date = DateTime.Now, + Mileage = odometer, + Description = existingRecord.Description, + Cost = existingRecord.Cost, + Notes = existingRecord.Notes, + Files = existingRecord.Files + }; + _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(newRecord); + } + } + return Json(result); + } + [HttpGet] + public IActionResult GetPlanRecordForEditById(int planRecordId) + { + var result = _planRecordDataAccess.GetPlanRecordById(planRecordId); + //convert to Input object. + var convertedResult = new PlanRecordInput + { + Id = result.Id, + Description = result.Description, + DateCreated = result.DateCreated.ToString("G"), + DateModified = result.DateModified.ToString("G"), + ImportMode = result.ImportMode, + Priority = result.Priority, + Progress = result.Progress, + Cost = result.Cost, + Notes = result.Notes, + VehicleId = result.VehicleId, + Files = result.Files + }; + return PartialView("_PlanRecordModal", convertedResult); + } + [HttpPost] + public IActionResult DeletePlanRecordById(int planRecordId) + { + var result = _planRecordDataAccess.DeletePlanRecordById(planRecordId); + return Json(result); + } + #endregion + #region "Odometer Records" + [TypeFilter(typeof(CollaboratorFilter))] + [HttpGet] + public IActionResult GetOdometerRecordsByVehicleId(int vehicleId) + { + var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId); + bool _useDescending = _config.GetUserConfig(User).UseDescending; + if (_useDescending) + { + result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList(); + } + else + { + result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList(); + } + return PartialView("_OdometerRecords", result); + } + [HttpPost] + public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord) + { + //move files from temp. + odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList(); + var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord()); + return Json(result); + } + [HttpGet] + public IActionResult GetAddOdometerRecordPartialView() + { + return PartialView("_OdometerRecordModal", new OdometerRecordInput()); + } + [HttpGet] + public IActionResult GetOdometerRecordForEditById(int odometerRecordId) + { + var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId); + //convert to Input object. + var convertedResult = new OdometerRecordInput + { + Id = result.Id, + Date = result.Date.ToShortDateString(), + Mileage = result.Mileage, + Notes = result.Notes, + VehicleId = result.VehicleId, + Files = result.Files + }; + return PartialView("_OdometerRecordModal", convertedResult); + } + [HttpPost] + public IActionResult DeleteOdometerRecordById(int odometerRecordId) + { + var result = _odometerRecordDataAccess.DeleteOdometerRecordById(odometerRecordId); + return Json(result); + } + #endregion } } diff --git a/Enum/ImportMode.cs b/Enum/ImportMode.cs index b2f5f01..f7fc5ed 100644 --- a/Enum/ImportMode.cs +++ b/Enum/ImportMode.cs @@ -10,6 +10,8 @@ ReminderRecord = 5, NoteRecord = 6, SupplyRecord = 7, - Dashboard = 8 + Dashboard = 8, + PlanRecord = 9, + OdometerRecord = 10 } } diff --git a/Enum/PlanPriority.cs b/Enum/PlanPriority.cs new file mode 100644 index 0000000..09237d1 --- /dev/null +++ b/Enum/PlanPriority.cs @@ -0,0 +1,9 @@ +namespace CarCareTracker.Models +{ + public enum PlanPriority + { + Critical = 0, + Normal = 1, + Low = 2 + } +} diff --git a/Enum/PlanProgress.cs b/Enum/PlanProgress.cs new file mode 100644 index 0000000..d14785a --- /dev/null +++ b/Enum/PlanProgress.cs @@ -0,0 +1,10 @@ +namespace CarCareTracker.Models +{ + public enum PlanProgress + { + Backlog = 0, + InProgress = 1, + Testing = 2, + Done = 3 + } +} diff --git a/Enum/ReminderMileageInterval.cs b/Enum/ReminderMileageInterval.cs new file mode 100644 index 0000000..1400aaa --- /dev/null +++ b/Enum/ReminderMileageInterval.cs @@ -0,0 +1,13 @@ +namespace CarCareTracker.Models +{ + public enum ReminderMileageInterval + { + FiveHundredMiles = 500, + OneThousandMiles = 1000, + ThreeThousandMiles = 3000, + FiveThousandMiles = 5000, + SevenThousandFiveHundredMiles = 7500, + TenThousandMiles = 10000, + FiftyThousandMiles = 50000 + } +} diff --git a/Enum/ReminderMonthInterval.cs b/Enum/ReminderMonthInterval.cs new file mode 100644 index 0000000..fb1148e --- /dev/null +++ b/Enum/ReminderMonthInterval.cs @@ -0,0 +1,10 @@ +namespace CarCareTracker.Models +{ + public enum ReminderMonthInterval + { + ThreeMonths = 3, + SixMonths = 6, + OneYear = 12, + FiveYears = 60 + } +} diff --git a/External/Implementations/OdometerRecordDataAccess.cs b/External/Implementations/OdometerRecordDataAccess.cs new file mode 100644 index 0000000..8695078 --- /dev/null +++ b/External/Implementations/OdometerRecordDataAccess.cs @@ -0,0 +1,57 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; +using LiteDB; + +namespace CarCareTracker.External.Implementations +{ + public class OdometerRecordDataAccess : IOdometerRecordDataAccess + { + private static string dbName = StaticHelper.DbName; + private static string tableName = "odometerrecords"; + public List GetOdometerRecordsByVehicleId(int vehicleId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var odometerRecords = table.Find(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId)); + return odometerRecords.ToList() ?? new List(); + }; + } + public OdometerRecord GetOdometerRecordById(int odometerRecordId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.FindById(odometerRecordId); + }; + } + public bool DeleteOdometerRecordById(int odometerRecordId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Delete(odometerRecordId); + return true; + }; + } + public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Upsert(odometerRecord); + return true; + }; + } + public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var odometerRecords = table.DeleteMany(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId)); + return true; + }; + } + } +} diff --git a/External/Implementations/PlanRecordDataAccess.cs b/External/Implementations/PlanRecordDataAccess.cs new file mode 100644 index 0000000..227fd14 --- /dev/null +++ b/External/Implementations/PlanRecordDataAccess.cs @@ -0,0 +1,57 @@ +using CarCareTracker.External.Interfaces; +using CarCareTracker.Helper; +using CarCareTracker.Models; +using LiteDB; + +namespace CarCareTracker.External.Implementations +{ + public class PlanRecordDataAccess : IPlanRecordDataAccess + { + private static string dbName = StaticHelper.DbName; + private static string tableName = "planrecords"; + public List GetPlanRecordsByVehicleId(int vehicleId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var planRecords = table.Find(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId)); + return planRecords.ToList() ?? new List(); + }; + } + public PlanRecord GetPlanRecordById(int planRecordId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + return table.FindById(planRecordId); + }; + } + public bool DeletePlanRecordById(int planRecordId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Delete(planRecordId); + return true; + }; + } + public bool SavePlanRecordToVehicle(PlanRecord planRecord) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + table.Upsert(planRecord); + return true; + }; + } + public bool DeleteAllPlanRecordsByVehicleId(int vehicleId) + { + using (var db = new LiteDatabase(dbName)) + { + var table = db.GetCollection(tableName); + var planRecords = table.DeleteMany(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId)); + return true; + }; + } + } +} diff --git a/External/Interfaces/IOdometerRecordDataAccess.cs b/External/Interfaces/IOdometerRecordDataAccess.cs new file mode 100644 index 0000000..2c64bcf --- /dev/null +++ b/External/Interfaces/IOdometerRecordDataAccess.cs @@ -0,0 +1,13 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.External.Interfaces +{ + public interface IOdometerRecordDataAccess + { + public List GetOdometerRecordsByVehicleId(int vehicleId); + public OdometerRecord GetOdometerRecordById(int odometerRecordId); + public bool DeleteOdometerRecordById(int odometerRecordId); + public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord); + public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId); + } +} diff --git a/External/Interfaces/IPlanRecordDataAccess.cs b/External/Interfaces/IPlanRecordDataAccess.cs new file mode 100644 index 0000000..1768801 --- /dev/null +++ b/External/Interfaces/IPlanRecordDataAccess.cs @@ -0,0 +1,13 @@ +using CarCareTracker.Models; + +namespace CarCareTracker.External.Interfaces +{ + public interface IPlanRecordDataAccess + { + public List GetPlanRecordsByVehicleId(int vehicleId); + public PlanRecord GetPlanRecordById(int planRecordId); + public bool DeletePlanRecordById(int planRecordId); + public bool SavePlanRecordToVehicle(PlanRecord planRecord); + public bool DeleteAllPlanRecordsByVehicleId(int vehicleId); + } +} diff --git a/Helper/FileHelper.cs b/Helper/FileHelper.cs index 823e579..ac9dfa8 100644 --- a/Helper/FileHelper.cs +++ b/Helper/FileHelper.cs @@ -68,7 +68,7 @@ namespace CarCareTracker.Helper var filesToUpload = Directory.GetFiles(imagePath); foreach(string file in filesToUpload) { - File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}"); + File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true); } } if (Directory.Exists(documentPath)) @@ -82,7 +82,7 @@ namespace CarCareTracker.Helper var filesToUpload = Directory.GetFiles(documentPath); foreach (string file in filesToUpload) { - File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}"); + File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true); } } if (File.Exists(dataPath)) diff --git a/Helper/ReminderHelper.cs b/Helper/ReminderHelper.cs index 7e9ff6e..0709b37 100644 --- a/Helper/ReminderHelper.cs +++ b/Helper/ReminderHelper.cs @@ -21,7 +21,8 @@ namespace CarCareTracker.Helper Mileage = reminder.Mileage, Description = reminder.Description, Notes = reminder.Notes, - Metric = reminder.Metric + Metric = reminder.Metric, + IsRecurring = reminder.IsRecurring }; if (reminder.Metric == ReminderMetric.Both) { diff --git a/MapProfile/ImportMappers.cs b/MapProfile/ImportMappers.cs index 258cdcd..93017ec 100644 --- a/MapProfile/ImportMappers.cs +++ b/MapProfile/ImportMappers.cs @@ -8,6 +8,8 @@ namespace CarCareTracker.MapProfile public ImportMapper() { Map(m => m.Date).Name(["date", "fuelup_date"]); + Map(m => m.DateCreated).Name(["datecreated"]); + Map(m => m.DateModified).Name(["datemodified"]); Map(m => m.Odometer).Name(["odometer"]); Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]); Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]); @@ -20,6 +22,9 @@ namespace CarCareTracker.MapProfile Map(m => m.PartSupplier).Name(["partsupplier"]); Map(m => m.PartQuantity).Name(["partquantity"]); Map(m => m.PartNumber).Name(["partnumber"]); + Map(m => m.Progress).Name(["progress"]); + Map(m => m.Type).Name(["type"]); + Map(m => m.Priority).Name(["priority"]); } } } diff --git a/Models/ImportModel.cs b/Models/ImportModel.cs index 87270d8..c03b6aa 100644 --- a/Models/ImportModel.cs +++ b/Models/ImportModel.cs @@ -6,6 +6,11 @@ public class ImportModel { public string Date { get; set; } + public string DateCreated { get; set; } + public string DateModified { get; set; } + public string Type { get; set; } + public string Priority { get; set; } + public string Progress { get; set; } public string Odometer { get; set; } public string Description { get; set; } public string Notes { get; set; } @@ -39,6 +44,12 @@ public string Notes { get; set; } public string Cost { get; set; } } + public class OdometerRecordExportModel + { + public string Date { get; set; } + public string Odometer { get; set; } + public string Notes { get; set; } + } public class TaxRecordExportModel { public string Date { get; set; } @@ -65,4 +76,16 @@ public string Metric { get; set; } public string Notes { get; set; } } + public class PlanRecordExportModel + { + public string DateCreated { get; set; } + public string DateModified { get; set; } + public string Description { get; set; } + public string Notes { get; set; } + public string Type { get; set; } + public string Priority { get; set; } + public string Progress { get; set; } + public string Cost { get; set; } + } + } diff --git a/Models/OdometerRecord/OdometerRecord.cs b/Models/OdometerRecord/OdometerRecord.cs new file mode 100644 index 0000000..3901789 --- /dev/null +++ b/Models/OdometerRecord/OdometerRecord.cs @@ -0,0 +1,12 @@ +namespace CarCareTracker.Models +{ + public class OdometerRecord + { + public int Id { get; set; } + public int VehicleId { get; set; } + public DateTime Date { get; set; } + public int Mileage { get; set; } + public string Notes { get; set; } + public List Files { get; set; } = new List(); + } +} diff --git a/Models/OdometerRecord/OdometerRecordInput.cs b/Models/OdometerRecord/OdometerRecordInput.cs new file mode 100644 index 0000000..27179a3 --- /dev/null +++ b/Models/OdometerRecord/OdometerRecordInput.cs @@ -0,0 +1,13 @@ +namespace CarCareTracker.Models +{ + public class OdometerRecordInput + { + public int Id { get; set; } + public int VehicleId { get; set; } + public string Date { get; set; } = DateTime.Now.ToShortDateString(); + public int Mileage { get; set; } + public string Notes { get; set; } + public List Files { get; set; } = new List(); + public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files }; } + } +} diff --git a/Models/PlanRecord/PlanCostItem.cs b/Models/PlanRecord/PlanCostItem.cs new file mode 100644 index 0000000..37bafa7 --- /dev/null +++ b/Models/PlanRecord/PlanCostItem.cs @@ -0,0 +1,8 @@ +namespace CarCareTracker.Models +{ + public class PlanCostItem + { + public string CostName { get; set; } + public decimal CostAmount { get; set; } + } +} diff --git a/Models/PlanRecord/PlanRecord.cs b/Models/PlanRecord/PlanRecord.cs new file mode 100644 index 0000000..98f724c --- /dev/null +++ b/Models/PlanRecord/PlanRecord.cs @@ -0,0 +1,17 @@ +namespace CarCareTracker.Models +{ + public class PlanRecord + { + public int Id { get; set; } + public int VehicleId { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateModified { get; set; } + public string Description { get; set; } + public string Notes { get; set; } + public List Files { get; set; } = new List(); + public ImportMode ImportMode { get; set; } + public PlanPriority Priority { get; set; } + public PlanProgress Progress { get; set; } + public decimal Cost { get; set; } + } +} diff --git a/Models/PlanRecord/PlanRecordInput.cs b/Models/PlanRecord/PlanRecordInput.cs new file mode 100644 index 0000000..19772e2 --- /dev/null +++ b/Models/PlanRecord/PlanRecordInput.cs @@ -0,0 +1,30 @@ +namespace CarCareTracker.Models +{ + public class PlanRecordInput + { + public int Id { get; set; } + public int VehicleId { get; set; } + public string DateCreated { get; set; } = DateTime.Now.ToShortDateString(); + public string DateModified { get; set; } = DateTime.Now.ToShortDateString(); + public string Description { get; set; } + public string Notes { get; set; } + public List Files { get; set; } = new List(); + public ImportMode ImportMode { get; set; } + public PlanPriority Priority { get; set; } + public PlanProgress Progress { get; set; } + public decimal Cost { get; set; } + public PlanRecord ToPlanRecord() { return new PlanRecord { + Id = Id, + VehicleId = VehicleId, + DateCreated = DateTime.Parse(DateCreated), + DateModified = DateTime.Parse(DateModified), + Description = Description, + Notes = Notes, + Files = Files, + ImportMode = ImportMode, + Cost = Cost, + Priority = Priority, + Progress = Progress + }; } + } +} diff --git a/Models/Reminder/ReminderRecord.cs b/Models/Reminder/ReminderRecord.cs index 13e8f7a..96ab3f0 100644 --- a/Models/Reminder/ReminderRecord.cs +++ b/Models/Reminder/ReminderRecord.cs @@ -8,6 +8,9 @@ public int Mileage { get; set; } public string Description { get; set; } public string Notes { get; set; } + public bool IsRecurring { get; set; } = false; + public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles; + public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear; public ReminderMetric Metric { get; set; } = ReminderMetric.Date; } } diff --git a/Models/Reminder/ReminderRecordInput.cs b/Models/Reminder/ReminderRecordInput.cs index f216390..5694afb 100644 --- a/Models/Reminder/ReminderRecordInput.cs +++ b/Models/Reminder/ReminderRecordInput.cs @@ -8,6 +8,9 @@ public int Mileage { get; set; } public string Description { get; set; } public string Notes { get; set; } + public bool IsRecurring { get; set; } = false; + public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles; + public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear; public ReminderMetric Metric { get; set; } = ReminderMetric.Date; public ReminderRecord ToReminderRecord() { return new ReminderRecord { Id = Id, @@ -16,6 +19,9 @@ Mileage = Mileage, Description = Description, Metric = Metric, + IsRecurring = IsRecurring, + ReminderMileageInterval = ReminderMileageInterval, + ReminderMonthInterval = ReminderMonthInterval, Notes = Notes }; } } } diff --git a/Models/Reminder/ReminderRecordViewModel.cs b/Models/Reminder/ReminderRecordViewModel.cs index 1f3f62e..5a9f65a 100644 --- a/Models/Reminder/ReminderRecordViewModel.cs +++ b/Models/Reminder/ReminderRecordViewModel.cs @@ -13,5 +13,9 @@ /// public ReminderMetric Metric { get; set; } = ReminderMetric.Date; public ReminderUrgency Urgency { get; set; } = ReminderUrgency.NotUrgent; + /// + /// Recurring Reminders + /// + public bool IsRecurring { get; set; } = false; } } diff --git a/Models/Upgrades/UpgradeRecord.cs b/Models/UpgradeRecord/UpgradeRecord.cs similarity index 100% rename from Models/Upgrades/UpgradeRecord.cs rename to Models/UpgradeRecord/UpgradeRecord.cs diff --git a/Models/Upgrades/UpgradeReportInput.cs b/Models/UpgradeRecord/UpgradeReportInput.cs similarity index 100% rename from Models/Upgrades/UpgradeReportInput.cs rename to Models/UpgradeRecord/UpgradeReportInput.cs diff --git a/Program.cs b/Program.cs index 571bd42..c0be2b6 100644 --- a/Program.cs +++ b/Program.cs @@ -23,6 +23,8 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); //configure helpers builder.Services.AddSingleton(); diff --git a/Views/API/Index.cshtml b/Views/API/Index.cshtml index 2f60ced..158caad 100644 --- a/Views/API/Index.cshtml +++ b/Views/API/Index.cshtml @@ -141,4 +141,38 @@ No Params(must be root user) -} \ No newline at end of file +} +
+
+ GET +
+
+ /api/vehicle/odometerrecords +
+
+ Returns a list of odometer records for the vehicle +
+
+ vehicleId - Id of Vehicle +
+
+
+
+ POST +
+
+ /api/vehicle/odometerrecords/add +
+
+ Returns a list of odometer records for the vehicle +
+
+ vehicleId - Id of Vehicle +
+ Body(form-data): {
+ date - Date to be entered
+ odometer - Odometer reading
+ notes - notes(optional)
+ } +
+
\ No newline at end of file diff --git a/Views/Home/_Settings.cshtml b/Views/Home/_Settings.cshtml index eb462a0..1795d29 100644 --- a/Views/Home/_Settings.cshtml +++ b/Views/Home/_Settings.cshtml @@ -70,6 +70,10 @@ +
  • + + +
  • @@ -90,6 +94,10 @@ +
  • + + +
  • @@ -106,6 +114,8 @@ Notes Reminders Supplies + Planner + Odometer @if (User.IsInRole(nameof(UserData.IsRootUser))) diff --git a/Views/Vehicle/Index.cshtml b/Views/Vehicle/Index.cshtml index 5bfba4c..0d7f3a5 100644 --- a/Views/Vehicle/Index.cshtml +++ b/Views/Vehicle/Index.cshtml @@ -18,6 +18,8 @@ + + }
    @@ -31,6 +33,12 @@ + + @@ -76,6 +84,12 @@ + + @@ -117,6 +131,8 @@
    +
    +
    @@ -67,6 +73,10 @@ getVehicleUpgradeRecords(vehicleId); } else if (mode == "SupplyRecord") { getVehicleSupplyRecords(vehicleId); + } else if (mode == "PlanRecord"){ + getVehiclePlanRecords(vehicleId); + } else if (mode == "OdometerRecord") { + getVehicleOdometerRecords(vehicleId); } } else { errorToast("An error has occurred, please double check the data and try again."); diff --git a/Views/Vehicle/_OdometerRecordModal.cshtml b/Views/Vehicle/_OdometerRecordModal.cshtml new file mode 100644 index 0000000..eae9515 --- /dev/null +++ b/Views/Vehicle/_OdometerRecordModal.cshtml @@ -0,0 +1,71 @@ +@model OdometerRecordInput +@{ + var isNew = Model.Id == 0; +} + + + + \ No newline at end of file diff --git a/Views/Vehicle/_OdometerRecords.cshtml b/Views/Vehicle/_OdometerRecords.cshtml new file mode 100644 index 0000000..4c37fa0 --- /dev/null +++ b/Views/Vehicle/_OdometerRecords.cshtml @@ -0,0 +1,69 @@ +@using CarCareTracker.Helper +@inject IConfigHelper config +@{ + var enableCsvImports = config.GetUserConfig(User).EnableCsvImports; + var hideZero = config.GetUserConfig(User).HideZero; +} +@model List +
    +
    +
    + @($"# of Odometer Records: {Model.Count()}") +
    +
    + @if (enableCsvImports) + { +
    + + + +
    + } + else + { + + } +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + + + + + + + + + @foreach (OdometerRecord odometerRecord in Model) + { + + + + + + } + +
    DateOdometerNotes
    @odometerRecord.Date.ToShortDateString()@odometerRecord.Mileage@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes)
    +
    +
    + + + \ No newline at end of file diff --git a/Views/Vehicle/_PlanRecordItem.cshtml b/Views/Vehicle/_PlanRecordItem.cshtml new file mode 100644 index 0000000..59cd73d --- /dev/null +++ b/Views/Vehicle/_PlanRecordItem.cshtml @@ -0,0 +1,49 @@ +@model PlanRecord +
    +
    +
    +
    + @if (Model.Progress == PlanProgress.Done) + { + @Model.Description + } else + { + @Model.Description + } +
    +
    + @Model.Cost.ToString("C2") +
    +
    +
    +
    + @if (Model.ImportMode == ImportMode.ServiceRecord) + { + + } + else if (Model.ImportMode == ImportMode.UpgradeRecord) + { + + } + else if (Model.ImportMode == ImportMode.RepairRecord) + { + + } +
    +
    + @if (Model.Priority == PlanPriority.Critical) + { + + } + else if (Model.Priority == PlanPriority.Normal) + { + + } + else if (Model.Priority == PlanPriority.Low) + { + + } +
    +
    +
    +
    \ No newline at end of file diff --git a/Views/Vehicle/_PlanRecordModal.cshtml b/Views/Vehicle/_PlanRecordModal.cshtml new file mode 100644 index 0000000..2a51dbf --- /dev/null +++ b/Views/Vehicle/_PlanRecordModal.cshtml @@ -0,0 +1,94 @@ +@model PlanRecordInput +@{ + var isNew = Model.Id == 0; +} + + + + \ No newline at end of file diff --git a/Views/Vehicle/_PlanRecords.cshtml b/Views/Vehicle/_PlanRecords.cshtml new file mode 100644 index 0000000..e291e2a --- /dev/null +++ b/Views/Vehicle/_PlanRecords.cshtml @@ -0,0 +1,100 @@ +@using CarCareTracker.Helper +@inject IConfigHelper config +@{ + var enableCsvImports = config.GetUserConfig(User).EnableCsvImports; + var hideZero = config.GetUserConfig(User).HideZero; + var backLogItems = Model.Where(x => x.Progress == PlanProgress.Backlog).OrderBy(x=>x.Priority); + var inProgressItems = Model.Where(x => x.Progress == PlanProgress.InProgress).OrderBy(x => x.Priority); + var testingItems = Model.Where(x => x.Progress == PlanProgress.Testing).OrderBy(x => x.Priority); + var doneItems = Model.Where(x => x.Progress == PlanProgress.Done).OrderBy(x => x.Priority); +} +@model List +
    +
    +
    + @($"# of Plan Records: {Model.Count()}") +
    +
    + @if (enableCsvImports) + { +
    + + + +
    + } + else + { + + } +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + Planned +
    +
    + @foreach (PlanRecord planRecord in backLogItems) + { + @await Html.PartialAsync("_PlanRecordItem", planRecord) + } +
    +
    +
    +
    + Doing +
    +
    + @foreach (PlanRecord planRecord in inProgressItems) + { + @await Html.PartialAsync("_PlanRecordItem", planRecord) + } +
    +
    +
    +
    + Testing +
    +
    + @foreach (PlanRecord planRecord in testingItems) + { + @await Html.PartialAsync("_PlanRecordItem", planRecord) + } +
    +
    +
    +
    + Done +
    +
    + @foreach (PlanRecord planRecord in doneItems) + { + @await Html.PartialAsync("_PlanRecordItem", planRecord) + } +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/Views/Vehicle/_ReminderRecordModal.cshtml b/Views/Vehicle/_ReminderRecordModal.cshtml index d939ade..4a9a027 100644 --- a/Views/Vehicle/_ReminderRecordModal.cshtml +++ b/Views/Vehicle/_ReminderRecordModal.cshtml @@ -41,6 +41,27 @@
    +
    + + +
    + + + +
    diff --git a/Views/Vehicle/_Report.cshtml b/Views/Vehicle/_Report.cshtml index 1b36458..4d05ac9 100644 --- a/Views/Vehicle/_Report.cshtml +++ b/Views/Vehicle/_Report.cshtml @@ -1,4 +1,5 @@ @model ReportViewModel +
    @@ -20,7 +21,8 @@
    -
    +
    +
    @@ -42,6 +44,7 @@
    +
    @@ -83,4 +86,5 @@
    +
    \ No newline at end of file diff --git a/wwwroot/css/site.css b/wwwroot/css/site.css index d7bf463..b1c7b46 100644 --- a/wwwroot/css/site.css +++ b/wwwroot/css/site.css @@ -35,18 +35,39 @@ html { overflow-y: auto; overflow-x: auto; } +.reportTabContainer { + max-height: 85vh; + overflow-y: auto; + overflow-x: auto; +} + +.vehicleDetailTabContainer.fixed { + height: 65vh; +} + +.swimlane{ + height:100%; +} +.swimlane.mid { + border-right-style: solid; +} +.swimlane.end { + border-left-style: solid; + } .showOnPrint { - display:none; + display: none; } @media print { .hideOnPrint { - display:none; + display: none; } - .showOnPrint{ - display:block !important; + + .showOnPrint { + display: block !important; } + .vehicleDetailTabContainer { background-color: #fff !important; height: 100%; @@ -60,18 +81,22 @@ html { color: #000 !important; overflow: visible; } + table { background-color: #fff !important; } + ul { border: 0px !important; background-color: #fff !important; } + li { color: #000 !important; border: 0px !important; background-color: #fff !important; } + td { color: #000 !important; border: hidden; @@ -81,7 +106,8 @@ html { td.col-1 { width: 10%; } - td.col-4.text-wrap{ + + td.col-4.text-wrap { width: 30%; } @@ -94,7 +120,7 @@ html { background-color: #fff !important; } - tr{ + tr { border: hidden; } } @@ -183,10 +209,10 @@ html { align-items: center; } - .lubelogger-navbar-button > button { - padding: 0; - font-size: 2rem; - } + .lubelogger-navbar-button > button { + padding: 0; + font-size: 2rem; + } .lubelogger-menu-icon { display: inline-block; @@ -196,7 +222,7 @@ html { .lubelogger-mobile-nav { background-color: var(--bs-body-bg); - height: 100vh; + height: max(100vh, 100%); width: 100%; position: absolute; top: 0; @@ -209,6 +235,7 @@ html { .lubelogger-mobile-nav-show { display: block; } + .lubelogger-mobile-nav > ul > li > .nav-link.active { color: #6ea8fe; } @@ -232,14 +259,38 @@ html { } } -.dropdown-menu.show{ +.dropdown-menu.show { z-index: 1030; } input[type="file"] { - max-width:100%; + max-width: 100%; } .uploadedFileName { - max-width:75%; + max-width: 75%; +} + +.taskCard { + max-height: 20vh; + padding:0.5rem; + overflow:hidden; + border-radius: 4px; + box-shadow: 0 6px 10px rgba(0,0,0,.08), 0 0 6px rgba(0,0,0,.05); + transition: .3s transform cubic-bezier(.155,1.105,.295,1.12),.3s box-shadow,.3s -webkit-transform cubic-bezier(.155,1.105,.295,1.12); + cursor: pointer; +} +.taskCard.nodrag{ + cursor:not-allowed; +} +.taskCard-title{ + font-size:1.5rem; + font-weight:300; + max-height:10vh; +} +[data-bs-theme=dark] .taskCard { + background-color: rgba(255,255,255,0.5); +} +[data-bs-theme=light] .taskCard { + background-color: rgba(80,80,80,0.25); } \ No newline at end of file diff --git a/wwwroot/defaults/odometersample.csv b/wwwroot/defaults/odometersample.csv new file mode 100644 index 0000000..14f0b80 --- /dev/null +++ b/wwwroot/defaults/odometersample.csv @@ -0,0 +1,2 @@ +Date,Odometer,Notes +1/1/2024,260001,test test diff --git a/wwwroot/defaults/plansample.csv b/wwwroot/defaults/plansample.csv new file mode 100644 index 0000000..cb9db53 --- /dev/null +++ b/wwwroot/defaults/plansample.csv @@ -0,0 +1,2 @@ +DateCreated,DateModified,Description,Notes,Type,Priority,Progress,Cost +1/19/2024 6:01:02 PM,1/19/2024 7:32:58 PM,Repair Exhaust,,RepairRecord,Normal,Testing,$50.00 diff --git a/wwwroot/js/odometerrecord.js b/wwwroot/js/odometerrecord.js new file mode 100644 index 0000000..78b6d2f --- /dev/null +++ b/wwwroot/js/odometerrecord.js @@ -0,0 +1,101 @@ +function showAddOdometerRecordModal() { + $.get('/Vehicle/GetAddOdometerRecordPartialView', function (data) { + if (data) { + $("#odometerRecordModalContent").html(data); + //initiate datepicker + initDatePicker($('#odometerRecordDate')); + $('#odometerRecordModal').modal('show'); + } + }); +} +function showEditOdometerRecordModal(odometerRecordId) { + $.get(`/Vehicle/GetOdometerRecordForEditById?odometerRecordId=${odometerRecordId}`, function (data) { + if (data) { + $("#odometerRecordModalContent").html(data); + //initiate datepicker + initDatePicker($('#odometerRecordDate')); + $('#odometerRecordModal').modal('show'); + } + }); +} +function hideAddOdometerRecordModal() { + $('#odometerRecordModal').modal('hide'); +} +function deleteOdometerRecord(odometerRecordId) { + $("#workAroundInput").show(); + Swal.fire({ + title: "Confirm Deletion?", + text: "Deleted Odometer Records cannot be restored.", + showCancelButton: true, + confirmButtonText: "Delete", + confirmButtonColor: "#dc3545" + }).then((result) => { + if (result.isConfirmed) { + $.post(`/Vehicle/DeleteOdometerRecordById?odometerRecordId=${odometerRecordId}`, function (data) { + if (data) { + hideAddOdometerRecordModal(); + successToast("Odometer Record Deleted"); + var vehicleId = GetVehicleId().vehicleId; + getVehicleOdometerRecords(vehicleId); + } else { + errorToast("An error has occurred, please try again later."); + } + }); + } else { + $("#workAroundInput").hide(); + } + }); +} +function saveOdometerRecordToVehicle(isEdit) { + //get values + var formValues = getAndValidateOdometerRecordValues(); + //validate + if (formValues.hasError) { + errorToast("Please check the form data"); + return; + } + //save to db. + $.post('/Vehicle/SaveOdometerRecordToVehicleId', { odometerRecord: formValues }, function (data) { + if (data) { + successToast(isEdit ? "Odometer Record Updated" : "Odometer Record Added."); + hideAddOdometerRecordModal(); + saveScrollPosition(); + getVehicleOdometerRecords(formValues.vehicleId); + if (formValues.addReminderRecord) { + setTimeout(function () { showAddReminderModal(formValues); }, 500); + } + } else { + errorToast("An error has occurred, please try again later."); + } + }) +} +function getAndValidateOdometerRecordValues() { + var serviceDate = $("#odometerRecordDate").val(); + var serviceMileage = $("#odometerRecordMileage").val(); + var serviceNotes = $("#odometerRecordNotes").val(); + var vehicleId = GetVehicleId().vehicleId; + var odometerRecordId = getOdometerRecordModelData().id; + //validation + var hasError = false; + if (serviceDate.trim() == '') { //eliminates whitespace. + hasError = true; + $("#odometerRecordDate").addClass("is-invalid"); + } else { + $("#odometerRecordDate").removeClass("is-invalid"); + } + if (serviceMileage.trim() == '' || parseInt(serviceMileage) < 0) { + hasError = true; + $("#odometerRecordMileage").addClass("is-invalid"); + } else { + $("#odometerRecordMileage").removeClass("is-invalid"); + } + return { + id: odometerRecordId, + hasError: hasError, + vehicleId: vehicleId, + date: serviceDate, + mileage: serviceMileage, + notes: serviceNotes, + files: uploadedFiles + } +} \ No newline at end of file diff --git a/wwwroot/js/planrecord.js b/wwwroot/js/planrecord.js new file mode 100644 index 0000000..4578e8e --- /dev/null +++ b/wwwroot/js/planrecord.js @@ -0,0 +1,179 @@ +function showAddPlanRecordModal() { + $.get('/Vehicle/GetAddPlanRecordPartialView', function (data) { + if (data) { + $("#planRecordModalContent").html(data); + //initiate datepicker + initDatePicker($('#planRecordDate')); + $('#planRecordModal').modal('show'); + } + }); +} +function showEditPlanRecordModal(planRecordId) { + $.get(`/Vehicle/GetPlanRecordForEditById?planRecordId=${planRecordId}`, function (data) { + if (data) { + $("#planRecordModalContent").html(data); + //initiate datepicker + initDatePicker($('#planRecordDate')); + $('#planRecordModal').modal('show'); + } + }); +} +function hideAddPlanRecordModal() { + $('#planRecordModal').modal('hide'); +} +function deletePlanRecord(planRecordId) { + $("#workAroundInput").show(); + Swal.fire({ + title: "Confirm Deletion?", + text: "Deleted Plan Records cannot be restored.", + showCancelButton: true, + confirmButtonText: "Delete", + confirmButtonColor: "#dc3545" + }).then((result) => { + if (result.isConfirmed) { + $.post(`/Vehicle/DeletePlanRecordById?planRecordId=${planRecordId}`, function (data) { + if (data) { + hideAddPlanRecordModal(); + successToast("Plan Record Deleted"); + var vehicleId = GetVehicleId().vehicleId; + getVehiclePlanRecords(vehicleId); + } else { + errorToast("An error has occurred, please try again later."); + } + }); + } else { + $("#workAroundInput").hide(); + } + }); +} +function savePlanRecordToVehicle(isEdit) { + //get values + var formValues = getAndValidatePlanRecordValues(); + //validate + if (formValues.hasError) { + errorToast("Please check the form data"); + return; + } + //save to db. + $.post('/Vehicle/SavePlanRecordToVehicleId', { planRecord: formValues }, function (data) { + if (data) { + successToast(isEdit ? "Plan Record Updated" : "Plan Record Added."); + hideAddPlanRecordModal(); + saveScrollPosition(); + getVehiclePlanRecords(formValues.vehicleId); + if (formValues.addReminderRecord) { + setTimeout(function () { showAddReminderModal(formValues); }, 500); + } + } else { + errorToast("An error has occurred, please try again later."); + } + }) +} +function getAndValidatePlanRecordValues() { + var planDescription = $("#planRecordDescription").val(); + var planCost = $("#planRecordCost").val(); + var planNotes = $("#planRecordNotes").val(); + var planType = $("#planRecordType").val(); + var planPriority = $("#planRecordPriority").val(); + var planProgress = $("#planRecordProgress").val(); + var planDateCreated = getPlanRecordModelData().dateCreated; + var vehicleId = GetVehicleId().vehicleId; + var planRecordId = getPlanRecordModelData().id; + //validation + var hasError = false; + if (planDescription.trim() == '') { + hasError = true; + $("#planRecordDescription").addClass("is-invalid"); + } else { + $("#planRecordDescription").removeClass("is-invalid"); + } + if (planCost.trim() == '' || !isValidMoney(planCost)) { + hasError = true; + $("#planRecordCost").addClass("is-invalid"); + } else { + $("#planRecordCost").removeClass("is-invalid"); + } + return { + id: planRecordId, + hasError: hasError, + vehicleId: vehicleId, + dateCreated: planDateCreated, + description: planDescription, + cost: planCost, + notes: planNotes, + files: uploadedFiles, + priority: planPriority, + progress: planProgress, + importMode: planType + } +} +//drag and drop stuff. + +let dragged = null; +let draggedId = 0; +function dragEnter(event) { + event.preventDefault(); +} +function dragStart(event, planRecordId) { + dragged = event.target; + draggedId = planRecordId; + event.dataTransfer.setData('text/plain', draggedId); +} +function dragOver(event) { + event.preventDefault(); +} +function dropBox(event, newProgress) { + if ($(event.target).hasClass("swimlane")) { + if (dragged.parentElement != event.target && event.target != dragged) { + updatePlanRecordProgress(newProgress); + } + } + event.preventDefault(); +} +function updatePlanRecordProgress(newProgress) { + if (draggedId > 0) { + if (newProgress == 'Done') { + //if user is marking the task as done, we will want them to enter the mileage so that we can auto-convert it. + Swal.fire({ + title: 'Mark Task as Done?', + html: `

    To confirm, please enter the current odometer reading on your vehicle, as we also need the current odometer to auto convert the task into the relevant record.

    + + `, + confirmButtonText: 'Confirm', + showCancelButton: true, + focusConfirm: false, + preConfirm: () => { + const odometer = $("#inputOdometer").val(); + if (!odometer || isNaN(odometer)) { + Swal.showValidationMessage(`Please enter an odometer reading`) + } + return { odometer } + }, + }).then(function (result) { + if (result.isConfirmed) { + $.post('/Vehicle/UpdatePlanRecordProgress', { planRecordId: draggedId, planProgress: newProgress, odometer: result.value.odometer }, function (data) { + if (data) { + successToast("Plan Progress Updated"); + var vehicleId = GetVehicleId().vehicleId; + getVehiclePlanRecords(vehicleId); + } else { + errorToast("An error has occurred, please try again later."); + } + }); + } + draggedId = 0; + }); + } else { + $.post('/Vehicle/UpdatePlanRecordProgress', { planRecordId: draggedId, planProgress: newProgress }, function (data) { + if (data) { + successToast("Plan Progress Updated"); + var vehicleId = GetVehicleId().vehicleId; + getVehiclePlanRecords(vehicleId); + } else { + errorToast("An error has occurred, please try again later."); + } + }); + draggedId = 0; + } + } +} \ No newline at end of file diff --git a/wwwroot/js/reminderrecord.js b/wwwroot/js/reminderrecord.js index 1bca595..1134cfa 100644 --- a/wwwroot/js/reminderrecord.js +++ b/wwwroot/js/reminderrecord.js @@ -69,12 +69,27 @@ function appendMileageToOdometer(increment) { reminderMileage += increment; $("#reminderMileage").val(reminderMileage); } + +function enableRecurring() { + var reminderIsRecurring = $("#reminderIsRecurring").is(":checked"); + if (reminderIsRecurring) { + $("#reminderRecurringMileage").attr('disabled', false); + $("#reminderRecurringMonth").attr('disabled', false); + } else { + $("#reminderRecurringMileage").attr('disabled', true); + $("#reminderRecurringMonth").attr('disabled', true); + } +} + function getAndValidateReminderRecordValues() { var reminderDate = $("#reminderDate").val(); var reminderMileage = $("#reminderMileage").val(); var reminderDescription = $("#reminderDescription").val(); var reminderNotes = $("#reminderNotes").val(); var reminderOption = $('#reminderOptions input:radio:checked').val(); + var reminderIsRecurring = $("#reminderIsRecurring").is(":checked"); + var reminderRecurringMonth = $("#reminderRecurringMonth").val(); + var reminderRecurringMileage = $("#reminderRecurringMileage").val(); var vehicleId = GetVehicleId().vehicleId; var reminderId = getReminderRecordModelData().id; //validation @@ -118,6 +133,9 @@ function getAndValidateReminderRecordValues() { mileage: reminderMileage, description: reminderDescription, notes: reminderNotes, - metric: reminderOption + metric: reminderOption, + isRecurring: reminderIsRecurring, + reminderMileageInterval: reminderRecurringMileage, + reminderMonthInterval: reminderRecurringMonth } } \ No newline at end of file diff --git a/wwwroot/js/vehicle.js b/wwwroot/js/vehicle.js index 393b942..3dc2ab8 100644 --- a/wwwroot/js/vehicle.js +++ b/wwwroot/js/vehicle.js @@ -33,6 +33,12 @@ $(document).ready(function () { case "supply-tab": getVehicleSupplyRecords(vehicleId); break; + case "plan-tab": + getVehiclePlanRecords(vehicleId); + break; + case "odometer-tab": + getVehicleOdometerRecords(vehicleId); + break; } switch (e.relatedTarget.id) { //clear out previous tabs with grids in them to help with performance case "servicerecord-tab": @@ -62,6 +68,12 @@ $(document).ready(function () { case "supply-tab": $("#supply-tab-pane").html(""); break; + case "plan-tab": + $("#plan-tab-pane").html(""); + break; + case "odometer-tab": + $("odometer-tab-pane").html(""); + break; } }); var defaultTab = GetDefaultTab().tab; @@ -93,6 +105,12 @@ $(document).ready(function () { case "SupplyRecord": getVehicleSupplyRecords(vehicleId); break; + case "PlanRecord": + getVehiclePlanRecords(vehicleId); + break; + case "OdometerRecord": + getVehicleOdometerRecords(vehicleId); + break; } }); @@ -113,6 +131,24 @@ function getVehicleServiceRecords(vehicleId) { } }); } +function getVehiclePlanRecords(vehicleId) { + $.get(`/Vehicle/GetPlanRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) { + if (data) { + $("#plan-tab-pane").html(data); + restoreScrollPosition(); + getVehicleHaveImportantReminders(vehicleId); + } + }); +} +function getVehicleOdometerRecords(vehicleId) { + $.get(`/Vehicle/GetOdometerRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) { + if (data) { + $("#odometer-tab-pane").html(data); + restoreScrollPosition(); + getVehicleHaveImportantReminders(vehicleId); + } + }); +} function getVehicleSupplyRecords(vehicleId) { $.get(`/Vehicle/GetSupplyRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) { if (data) {