Merge pull request #125 from hargata/Hargata/to.do

V1.0.7 - Planner(Kanban), Recurring Reminders, Odometer Tab
This commit is contained in:
Hargata Softworks
2024-01-20 09:54:10 -07:00
committed by GitHub
44 changed files with 1541 additions and 30 deletions

View File

@@ -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;
}
}

View File

@@ -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 {
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<ReminderRecordViewModel> 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
}
}

View File

@@ -10,6 +10,8 @@
ReminderRecord = 5,
NoteRecord = 6,
SupplyRecord = 7,
Dashboard = 8
Dashboard = 8,
PlanRecord = 9,
OdometerRecord = 10
}
}

9
Enum/PlanPriority.cs Normal file
View File

@@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public enum PlanPriority
{
Critical = 0,
Normal = 1,
Low = 2
}
}

10
Enum/PlanProgress.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public enum PlanProgress
{
Backlog = 0,
InProgress = 1,
Testing = 2,
Done = 3
}
}

View File

@@ -0,0 +1,13 @@
namespace CarCareTracker.Models
{
public enum ReminderMileageInterval
{
FiveHundredMiles = 500,
OneThousandMiles = 1000,
ThreeThousandMiles = 3000,
FiveThousandMiles = 5000,
SevenThousandFiveHundredMiles = 7500,
TenThousandMiles = 10000,
FiftyThousandMiles = 50000
}
}

View File

@@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public enum ReminderMonthInterval
{
ThreeMonths = 3,
SixMonths = 6,
OneYear = 12,
FiveYears = 60
}
}

View File

@@ -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<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<OdometerRecord>(tableName);
var odometerRecords = table.Find(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
return odometerRecords.ToList() ?? new List<OdometerRecord>();
};
}
public OdometerRecord GetOdometerRecordById(int odometerRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<OdometerRecord>(tableName);
return table.FindById(odometerRecordId);
};
}
public bool DeleteOdometerRecordById(int odometerRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<OdometerRecord>(tableName);
table.Delete(odometerRecordId);
return true;
};
}
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<OdometerRecord>(tableName);
table.Upsert(odometerRecord);
return true;
};
}
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<OdometerRecord>(tableName);
var odometerRecords = table.DeleteMany(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@@ -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<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.Find(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
return planRecords.ToList() ?? new List<PlanRecord>();
};
}
public PlanRecord GetPlanRecordById(int planRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<PlanRecord>(tableName);
return table.FindById(planRecordId);
};
}
public bool DeletePlanRecordById(int planRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<PlanRecord>(tableName);
table.Delete(planRecordId);
return true;
};
}
public bool SavePlanRecordToVehicle(PlanRecord planRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<PlanRecord>(tableName);
table.Upsert(planRecord);
return true;
};
}
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IOdometerRecordDataAccess
{
public List<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId);
public OdometerRecord GetOdometerRecordById(int odometerRecordId);
public bool DeleteOdometerRecordById(int odometerRecordId);
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord);
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId);
}
}

View File

@@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IPlanRecordDataAccess
{
public List<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId);
public PlanRecord GetPlanRecordById(int planRecordId);
public bool DeletePlanRecordById(int planRecordId);
public bool SavePlanRecordToVehicle(PlanRecord planRecord);
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId);
}
}

View File

@@ -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))

View File

@@ -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)
{

View File

@@ -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"]);
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@@ -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<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files }; }
}
}

View File

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

View File

@@ -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<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public ImportMode ImportMode { get; set; }
public PlanPriority Priority { get; set; }
public PlanProgress Progress { get; set; }
public decimal Cost { get; set; }
}
}

View File

@@ -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<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
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
}; }
}
}

View File

@@ -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;
}
}

View File

@@ -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 }; }
}
}

View File

@@ -13,5 +13,9 @@
/// </summary>
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
public ReminderUrgency Urgency { get; set; } = ReminderUrgency.NotUrgent;
/// <summary>
/// Recurring Reminders
/// </summary>
public bool IsRecurring { get; set; } = false;
}
}

View File

@@ -23,6 +23,8 @@ builder.Services.AddSingleton<ITokenRecordDataAccess, TokenRecordDataAccess>();
builder.Services.AddSingleton<IUserAccessDataAccess, UserAccessDataAccess>();
builder.Services.AddSingleton<IUserConfigDataAccess, UserConfigDataAccess>();
builder.Services.AddSingleton<ISupplyRecordDataAccess, SupplyRecordDataAccess>();
builder.Services.AddSingleton<IPlanRecordDataAccess, PlanRecordDataAccess>();
builder.Services.AddSingleton<IOdometerRecordDataAccess, OdometerRecordDataAccess>();
//configure helpers
builder.Services.AddSingleton<IFileHelper, FileHelper>();

View File

@@ -142,3 +142,37 @@
</div>
</div>
}
<div class="row">
<div class="col-1">
GET
</div>
<div class="col-5">
<code>/api/vehicle/odometerrecords</code>
</div>
<div class="col-3">
Returns a list of odometer records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row">
<div class="col-1">
POST
</div>
<div class="col-5">
<code>/api/vehicle/odometerrecords/add</code>
</div>
<div class="col-3">
Returns a list of odometer records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
notes - notes(optional)<br />
}
</div>
</div>

View File

@@ -70,6 +70,10 @@
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="GasRecord" id="gasRecordTab" @(Model.VisibleTabs.Contains(ImportMode.GasRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="gasRecordTab">Fuel</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="OdometerRecord" id="odometerRecordTab" @(Model.VisibleTabs.Contains(ImportMode.OdometerRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="odometerRecordTab">Odometer</label>
</li>
</ul>
</div>
<div class="col-12 col-md-6">
@@ -90,6 +94,10 @@
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="SupplyRecord" id="supplyRecordTab" @(Model.VisibleTabs.Contains(ImportMode.SupplyRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="supplyRecordTab">Supplies</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="PlanRecord" id="planRecordTab" @(Model.VisibleTabs.Contains(ImportMode.PlanRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="planRecordTab">Planner</label>
</li>
</ul>
</div>
</div>
@@ -106,6 +114,8 @@
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.NoteRecord)) value="NoteRecord">Notes</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.ReminderRecord)) value="ReminderRecord">Reminders</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.SupplyRecord)) value="SupplyRecord">Supplies</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.PlanRecord)) value="PlanRecord">Planner</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.OdometerRecord)) value="OdometerRecord">Odometer</!option>
</select>
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))

View File

@@ -18,6 +18,8 @@
<script src="~/js/note.js" asp-append-version="true"></script>
<script src="~/js/reports.js" asp-append-version="true"></script>
<script src="~/js/supplyrecord.js" asp-append-version="true"></script>
<script src="~/js/planrecord.js" asp-append-version="true"></script>
<script src="~/js/odometerrecord.js" asp-append-version="true"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
}
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
@@ -31,6 +33,12 @@
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>Dashboard</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-bar-chart-steps me-2"></i>Planner</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-speedometer me-2"></i>Odometer</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>Service Records</span></button>
</li>
@@ -76,6 +84,12 @@
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Dash</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-bar-chart-steps me-2"></i>Planner</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-speedometer me-2"></i>Odometer</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
</li>
@@ -117,6 +131,8 @@
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.Dashboard)" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.SupplyRecord)" id="supply-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.PlanRecord)" id="plan-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.OdometerRecord)" id="odometer-tab-pane" role="tabpanel" tabindex="0"></div>
</div>
</div>
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">

View File

@@ -28,6 +28,12 @@
} else if (Model == ImportMode.SupplyRecord)
{
<a class="btn btn-link" href="/defaults/supplysample.csv" target="_blank">Download Sample</a>
} else if (Model == ImportMode.PlanRecord)
{
<a class="btn btn-link" href="/defaults/plansample.csv" target="_blank">Download Sample</a>
} else if (Model == ImportMode.OdometerRecord)
{
<a class="btn btn-link" href="/defaults/odometersample.csv" target="_blank">Download Sample</a>
}
</div>
</div>
@@ -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.");

View File

@@ -0,0 +1,71 @@
@model OdometerRecordInput
@{
var isNew = Model.Id == 0;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? "Add New Odometer Record" : "Edit Odometer Record")</h5>
<button type="button" class="btn-close" onclick="hideAddOdometerRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="odometerRecordDate">Date</label>
<div class="input-group">
<input type="text" id="odometerRecordDate" class="form-control" placeholder="Date recorded" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="odometerRecordMileage">Odometer</label>
<input type="number" id="odometerRecordMileage" class="form-control" placeholder="Odometer reading" value="@(isNew ? "" : Model.Mileage)">
</div>
<div class="col-md-6 col-12">
<label for="odometerRecordNotes">Notes(optional)</label>
<textarea id="odometerRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="odometerRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
</div>
}
else
{
<label for="odometerRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
}
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteOdometerRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddOdometerRecordModal()">Cancel</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle()">Add New Odometer Record</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle(true)">Edit Odometer Record</button>
}
</div>
<script>
var uploadedFiles = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
function getOdometerRecordModelData() {
return { id: @Model.Id}
}
</script>

View File

@@ -0,0 +1,69 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@{
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
var hideZero = config.GetUserConfig(User).HideZero;
}
@model List<OdometerRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success">@($"# of Odometer Records: {Model.Count()}")</span>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddOdometerRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Odometer Record</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('OdometerRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('OdometerRecord')">Export to CSV</a></li>
</ul>
</div>
}
else
{
<button onclick="showAddOdometerRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Odometer Record</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="/defaults/lubelogger_logo.png" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1">Date</th>
<th scope="col" class="col-3">Odometer</th>
<th scope="col" class="col-7 col-xl-8">Notes</th>
</tr>
</thead>
<tbody>
@foreach (OdometerRecord odometerRecord in Model)
{
<tr class="d-flex" style="cursor:pointer;" onclick="showEditOdometerRecordModal(@odometerRecord.Id)">
<td class="col-2 col-xl-1">@odometerRecord.Date.ToShortDateString()</td>
<td class="col-3">@odometerRecord.Mileage</td>
<td class="col-7 col-xl-8 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="odometerRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="odometerRecordModalContent">
</div>
</div>
</div>

View File

@@ -0,0 +1,49 @@
@model PlanRecord
<div class="taskCard @(Model.Progress == PlanProgress.Done ? "nodrag" : "") text-dark user-select-none mb-2" draggable="@(Model.Progress == PlanProgress.Done ? "false" : "true")" ondragstart="dragStart(event, @Model.Id)" onclick="@(Model.Progress == PlanProgress.Done ? $"deletePlanRecord({Model.Id})" : $"showEditPlanRecordModal({Model.Id})")">
<div class="card-body">
<div class="row">
<div class="col-12 col-lg-8 text-truncate">
@if (Model.Progress == PlanProgress.Done)
{
<span class="taskCard-title text-truncate"><s>@Model.Description</s></span>
} else
{
<span class="taskCard-title text-truncate">@Model.Description</span>
}
</div>
<div class="col-12 col-lg-4 d-flex align-items-center">
<span class="text-truncate">@Model.Cost.ToString("C2")</span>
</div>
</div>
<div class="row">
<div class="col-6 col-md-1">
@if (Model.ImportMode == ImportMode.ServiceRecord)
{
<i class="bi bi-card-checklist"></i>
}
else if (Model.ImportMode == ImportMode.UpgradeRecord)
{
<i class="bi bi-wrench-adjustable"></i>
}
else if (Model.ImportMode == ImportMode.RepairRecord)
{
<i class="bi bi-exclamation-octagon"></i>
}
</div>
<div class="col-6 col-md-1">
@if (Model.Priority == PlanPriority.Critical)
{
<i class="bi bi-fire"></i>
}
else if (Model.Priority == PlanPriority.Normal)
{
<i class="bi bi-water"></i>
}
else if (Model.Priority == PlanPriority.Low)
{
<i class="bi bi-snow"></i>
}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,94 @@
@model PlanRecordInput
@{
var isNew = Model.Id == 0;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? "Add New Plan Record" : "Edit Plan Record")</h5>
<button type="button" class="btn-close" onclick="hideAddPlanRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="planRecordDescription">Description</label>
<input type="text" id="planRecordDescription" class="form-control" placeholder="Describe the Plan" value="@Model.Description">
<label for="planRecordCost">Cost</label>
<input type="text" id="planRecordCost" class="form-control" placeholder="Cost of the Plan" value="@Model.Cost">
<label for="planRecordType">Type</label>
<select class="form-select" id="planRecordType">
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>Service</!option>
<!option value="RepairRecord" @(Model.ImportMode == ImportMode.RepairRecord ? "selected" : "")>Repair</!option>
<!option value="UpgradeRecord" @(Model.ImportMode == ImportMode.UpgradeRecord ? "selected" : "")>Upgrade</!option>
</select>
<label for="planRecordPriority">Priority</label>
<select class="form-select" id="planRecordPriority">
<!option value="Critical" @(Model.Priority == PlanPriority.Critical ? "selected" : "")>Critical</!option>
<!option value="Normal" @(Model.Priority == PlanPriority.Normal || isNew ? "selected" : "")>Normal</!option>
<!option value="Low" @(Model.Priority == PlanPriority.Low ? "selected" : "")>Low</!option>
</select>
<label for="planRecordProgress">Current Stage</label>
<select class="form-select" id="planRecordProgress">
<!option value="Backlog" @(Model.Progress == PlanProgress.Backlog ||isNew ? "selected" : "")>Planned</!option>
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>Doing</!option>
<!option value="Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>Testing</!option>
</select>
@if (!isNew)
{
<label>Date Created: @Model.DateCreated</label>
<label>Last Modified: @Model.DateModified</label>
}
</div>
<div class="col-md-6 col-12">
<label for="planRecordNotes">Notes(optional)</label>
<textarea id="planRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="planRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
</div>
}
else
{
<label for="planRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
}
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deletePlanRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">Cancel</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle()">Add New Plan Record</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle(true)">Edit Plan Record</button>
}
</div>
<script>
var uploadedFiles = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
function getPlanRecordModelData() {
return {
id: @Model.Id,
dateCreated: "@Model.DateCreated"
}
}
</script>

View File

@@ -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<PlanRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success">@($"# of Plan Records: {Model.Count()}")</span>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddPlanRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Plan Record</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('PlanRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('PlanRecord')">Export to CSV</a></li>
</ul>
</div>
}
else
{
<button onclick="showAddPlanRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Plan Record</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer fixed">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="/defaults/lubelogger_logo.png" />
</div>
</div>
<div class="row swimlane">
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">Planned</span>
</div>
</div>
@foreach (PlanRecord planRecord in backLogItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">Doing</span>
</div>
</div>
@foreach (PlanRecord planRecord in inProgressItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Testing')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">Testing</span>
</div>
</div>
@foreach (PlanRecord planRecord in testingItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane end" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
<div class="row">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">Done</span>
</div>
</div>
@foreach (PlanRecord planRecord in doneItems)
{
@await Html.PartialAsync("_PlanRecordItem", planRecord)
}
</div>
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="planRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="planRecordModalContent">
</div>
</div>
</div>

View File

@@ -41,6 +41,27 @@
<div class="col-md-6 col-12">
<label for="reminderNotes">Notes(optional)</label>
<textarea id="reminderNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onChange="enableRecurring()" role="switch" id="reminderIsRecurring" checked="@Model.IsRecurring">
<label class="form-check-label" for="reminderIsRecurring">Is Recurring</label>
</div>
<label for="reminderRecurringMileage">Odometer</label>
<select class="form-select" id="reminderRecurringMileage" @(Model.IsRecurring ? "" : "disabled")>
<!option value="FiveHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiveHundredMiles || isNew ? "selected" : "")>500 mi. / Km</!option>
<!option value="OneThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneThousandMiles ? "selected" : "")>1000 mi. / Km</!option>
<!option value="ThreeThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.ThreeThousandMiles ? "selected" : "")>3000 mi. / Km</!option>
<!option value="FiveThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiveThousandMiles || isNew ? "selected" : "")>5000 mi. / Km</!option>
<!option value="SevenThousandFiveHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.SevenThousandFiveHundredMiles ? "selected" : "")>7500 mi. / Km</!option>
<!option value="TenThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.TenThousandMiles ? "selected" : "")>10000 mi. / Km</!option>
<!option value="FiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyThousandMiles ? "selected" : "")>50000 mi. / Km</!option>
</select>
<label for="reminderRecurringMonth">Month</label>
<select class="form-select" id="reminderRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>3 Months</!option>
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>6 Months</!option>
<!option value="OneYear" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneYear ? "selected" : "")>1 Year</!option>
<!option value="FiveYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>5 Years</!option>
</select>
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
@model ReportViewModel
<div class="container reportTabContainer">
<div class="row hideOnPrint">
<div class="col-md-3 col-12 mt-2">
<div class="row">
@@ -20,7 +21,8 @@
</div>
<div class="col-md-6 col-12 mt-2">
<div class="row">
<div class="col-12 d-flex justify-content-center reportsCheckBoxContainer">
<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(this)" type="checkbox" id="serviceExpenseCheck" value="1" checked>
<label class="form-check-label" for="serviceExpenseCheck">Service</label>
@@ -42,6 +44,7 @@
<label class="form-check-label" for="taxExpenseCheck">Tax</label>
</div>
</div>
<div class="col-md-1 d-sm-none d-md-block"></div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="gasCostByMonthReportContent">
@@ -83,4 +86,5 @@
</div>
</div>
</div>
</div>
<div id="vehicleHistoryReport" class="showOnPrint"></div>

View File

@@ -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;
}
}
@@ -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);
}

View File

@@ -0,0 +1,2 @@
Date,Odometer,Notes
1/1/2024,260001,test test
1 Date Odometer Notes
2 1/1/2024 260001 test test

View File

@@ -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
1 DateCreated DateModified Description Notes Type Priority Progress Cost
2 1/19/2024 6:01:02 PM 1/19/2024 7:32:58 PM Repair Exhaust RepairRecord Normal Testing $50.00

View File

@@ -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
}
}

179
wwwroot/js/planrecord.js Normal file
View File

@@ -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: `<p>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.</p>
<input type="text" id="inputOdometer" class="swal2-input" placeholder="Odometer Reading">
`,
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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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) {