Merge pull request #1105 from hargata/Hargata/cost.data.fix

Version 1.5.4 Changes
This commit is contained in:
Hargata Softworks 2025-11-04 15:47:06 -07:00 committed by GitHub
commit 3d33a798f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 2629 additions and 172 deletions

View File

@ -13,9 +13,9 @@
<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="MailKit" Version="4.13.0" />
<PackageReference Include="MailKit" Version="4.14.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.14.0" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="Npgsql" Version="9.0.4" />
</ItemGroup>
</Project>

View File

@ -24,6 +24,8 @@ namespace CarCareTracker.Controllers
private readonly ISupplyRecordDataAccess _supplyRecordDataAccess;
private readonly IPlanRecordDataAccess _planRecordDataAccess;
private readonly IPlanRecordTemplateDataAccess _planRecordTemplateDataAccess;
private readonly IInspectionRecordDataAccess _inspectionRecordDataAccess;
private readonly IInspectionRecordTemplateDataAccess _inspectionRecordTemplateDataAccess;
private readonly IUserAccessDataAccess _userAccessDataAccess;
private readonly IUserRecordDataAccess _userRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
@ -49,6 +51,8 @@ namespace CarCareTracker.Controllers
ISupplyRecordDataAccess supplyRecordDataAccess,
IPlanRecordDataAccess planRecordDataAccess,
IPlanRecordTemplateDataAccess planRecordTemplateDataAccess,
IInspectionRecordDataAccess inspectionRecordDataAccess,
IInspectionRecordTemplateDataAccess inspectionRecordTemplateDataAccess,
IUserAccessDataAccess userAccessDataAccess,
IUserRecordDataAccess userRecordDataAccess,
IMailHelper mailHelper,
@ -71,6 +75,8 @@ namespace CarCareTracker.Controllers
_supplyRecordDataAccess = supplyRecordDataAccess;
_planRecordDataAccess = planRecordDataAccess;
_planRecordTemplateDataAccess = planRecordTemplateDataAccess;
_inspectionRecordDataAccess = inspectionRecordDataAccess;
_inspectionRecordTemplateDataAccess = inspectionRecordTemplateDataAccess;
_userAccessDataAccess = userAccessDataAccess;
_userRecordDataAccess = userRecordDataAccess;
_mailHelper = mailHelper;
@ -533,7 +539,8 @@ namespace CarCareTracker.Controllers
VehicleId = vehicleId,
Date = DateTime.Parse(input.Date),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
Mileage = int.Parse(input.Odometer),
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.ServiceRecord, serviceRecord.Id, serviceRecord.Description)
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
@ -728,7 +735,8 @@ namespace CarCareTracker.Controllers
VehicleId = vehicleId,
Date = DateTime.Parse(input.Date),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
Mileage = int.Parse(input.Odometer),
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.RepairRecord, repairRecord.Id, repairRecord.Description)
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
@ -925,7 +933,8 @@ namespace CarCareTracker.Controllers
VehicleId = vehicleId,
Date = DateTime.Parse(input.Date),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
Mileage = int.Parse(input.Odometer),
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.UpgradeRecord, upgradeRecord.Id, upgradeRecord.Description)
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
@ -1546,7 +1555,8 @@ namespace CarCareTracker.Controllers
VehicleId = vehicleId,
Date = DateTime.Parse(input.Date),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
Mileage = int.Parse(input.Odometer),
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.GasRecord, gasRecord.Id, $"Gas Record - {gasRecord.Mileage.ToString()}")
};
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
@ -1652,7 +1662,7 @@ namespace CarCareTracker.Controllers
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/reminders")]
public IActionResult Reminders(int vehicleId, List<ReminderUrgency> urgencies)
public IActionResult Reminders(int vehicleId, List<ReminderUrgency> urgencies, string tags)
{
if (vehicleId == default)
{
@ -1668,6 +1678,11 @@ namespace CarCareTracker.Controllers
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var reminderResults = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
reminderResults.RemoveAll(x => !urgencies.Contains(x.Urgency));
if (!string.IsNullOrWhiteSpace(tags))
{
var tagsFilter = tags.Split(' ').Distinct();
reminderResults.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
}
var results = reminderResults.Select(x=> new ReminderAPIExportModel { Id = x.Id.ToString(), Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), UserMetric = x.UserMetric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString(), DueDays = x.DueDays.ToString(), DueDistance = x.DueMileage.ToString(), Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
@ -1904,7 +1919,7 @@ namespace CarCareTracker.Controllers
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
[Route("/api/vehicle/reminders/send")]
public IActionResult SendReminders(List<ReminderUrgency> urgencies)
public IActionResult SendReminders(List<ReminderUrgency> urgencies, string tags)
{
if (!urgencies.Any())
{
@ -1914,6 +1929,7 @@ namespace CarCareTracker.Controllers
var vehicles = _dataAccess.GetVehicles();
List<OperationResponse> operationResponses = new List<OperationResponse>();
var defaultEmailAddress = _config.GetDefaultReminderEmail();
List<string> tagsFilter = !string.IsNullOrWhiteSpace(tags) ? tags.Split(' ').Distinct().ToList() : new List<string>();
foreach(Vehicle vehicle in vehicles)
{
var vehicleId = vehicle.Id;
@ -1922,6 +1938,10 @@ namespace CarCareTracker.Controllers
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).OrderByDescending(x => x.Urgency).ToList();
results.RemoveAll(x => !urgencies.Contains(x.Urgency));
if (tagsFilter.Any())
{
results.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
}
if (!results.Any())
{
continue;
@ -2004,6 +2024,8 @@ namespace CarCareTracker.Controllers
vehicleDocuments.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_inspectionRecordTemplateDataAccess.GetInspectionRecordTemplatesByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
}
//shop supplies
vehicleDocuments.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));

View File

@ -102,7 +102,7 @@ namespace CarCareTracker.Controllers
string filePath = Path.Combine(uploadPath, fileName);
string fileData = string.Join("\r\n", coordinates);
System.IO.File.WriteAllText(filePath, fileData);
var uploadedFile = new UploadedFiles { Name = "coordinates.csv", Location = Path.Combine("/", uploadDirectory, fileName) };
var uploadedFile = new UploadedFiles { Name = "coordinates.csv", Location = Path.Combine("/", uploadDirectory, fileName), IsPending = true };
return Json(uploadedFile);
}
public IActionResult PreviewFile(string fileName, string fileLocation)

View File

@ -321,13 +321,13 @@ namespace CarCareTracker.Controllers
[HttpPost]
public IActionResult ExportTranslation(IFormFile file)
{
var translationData = new Dictionary<string, string>();
var result = string.Empty;
using (var sReader = new StreamReader(file.OpenReadStream()))
{
var fileName = $"{Guid.NewGuid()}.json";
var sData = sReader.ReadToEnd();
translationData = JsonSerializer.Deserialize<Dictionary<string, string>>(sData);
result = _fileHelper.WriteFileToTemp(fileName, sData);
}
var result = _translationHelper.ExportTranslation(translationData);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]

View File

@ -52,7 +52,9 @@ namespace CarCareTracker.Controllers
"CREATE TABLE IF NOT EXISTS app.tokenrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)",
"CREATE TABLE IF NOT EXISTS app.userconfigrecords (id INT primary key, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.useraccessrecords (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))",
"CREATE TABLE IF NOT EXISTS app.extrafields (id INT primary key, data jsonb not null)"
"CREATE TABLE IF NOT EXISTS app.extrafields (id INT primary key, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.inspectionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.inspectionrecordtemplates (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)"
};
foreach(string cmd in cmds)
{
@ -99,6 +101,8 @@ namespace CarCareTracker.Controllers
var useraccessrecords = new List<UserAccess>();
var extrafields = new List<RecordExtraField>();
var inspectionrecords = new List<InspectionRecord>();
var inspectionrecordtemplates = new List<InspectionRecordInput>();
#region "Part1"
string cmd = $"SELECT data FROM app.vehicles";
using (var ctext = pgDataSource.CreateCommand(cmd))
@ -416,6 +420,41 @@ namespace CarCareTracker.Controllers
table.Upsert(record);
};
}
cmd = $"SELECT data FROM app.inspectionrecords";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
inspectionrecords.Add(JsonSerializer.Deserialize<InspectionRecord>(reader["data"] as string));
}
}
foreach (var record in inspectionrecords)
{
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecord>("inspectionrecords");
table.Upsert(record);
}
;
}
cmd = $"SELECT data FROM app.inspectionrecordtemplates";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
inspectionrecordtemplates.Add(JsonSerializer.Deserialize<InspectionRecordInput>(reader["data"] as string));
}
}
foreach (var record in inspectionrecordtemplates)
{
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecordInput>("inspectionrecordtemplates");
table.Upsert(record);
};
}
#endregion
var destFilePath = $"{fullFolderPath}.zip";
ZipFile.CreateFromDirectory(fullFolderPath, destFilePath);
@ -464,6 +503,8 @@ namespace CarCareTracker.Controllers
var useraccessrecords = new List<UserAccess>();
var extrafields = new List<RecordExtraField>();
var inspectionrecords = new List<InspectionRecord>();
var inspectionrecordtemplates = new List<InspectionRecordInput>();
#region "Part1"
using (var db = new LiteDatabase(fullFileName))
{
@ -743,6 +784,38 @@ namespace CarCareTracker.Controllers
ctext.ExecuteNonQuery();
}
}
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecord>("inspectionrecords");
inspectionrecords = table.FindAll().ToList();
};
foreach (var record in inspectionrecords)
{
string cmd = $"INSERT INTO app.inspectionrecords (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.inspectionrecords_id_seq', (SELECT MAX(id) from app.inspectionrecords));";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
using (var db = new LiteDatabase(fullFileName))
{
var table = db.GetCollection<InspectionRecordInput>("inspectionrecordtemplates");
inspectionrecordtemplates = table.FindAll().ToList();
};
foreach (var record in inspectionrecordtemplates)
{
string cmd = $"INSERT INTO app.inspectionrecordtemplates (id, vehicleId, data) VALUES(@id, @vehicleId, CAST(@data AS jsonb)); SELECT setval('app.inspectionrecordtemplates_id_seq', (SELECT MAX(id) from app.inspectionrecordtemplates));";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
#endregion
return Json(OperationResponse.Succeed("Data Imported Successfully"));
}

View File

@ -40,22 +40,24 @@ namespace CarCareTracker.Controllers
{
return Json(false);
}
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
gasRecord.Files = gasRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var convertedRecord = gasRecord.ToGasRecord();
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(convertedRecord, gasRecord.Id == default ? "gasrecord.add" : "gasrecord.update", User.Identity.Name));
}
if (convertedRecord.Id != default && gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(gasRecord.Date),
VehicleId = gasRecord.VehicleId,
Mileage = gasRecord.Mileage,
Notes = $"Auto Insert From Gas Record. {gasRecord.Notes}"
Notes = $"Auto Insert From Gas Record. {gasRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.GasRecord, convertedRecord.Id, $"Gas Record - {gasRecord.Mileage.ToString()}")
});
}
gasRecord.Files = gasRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(gasRecord.ToGasRecord(), gasRecord.Id == default ? "gasrecord.add" : "gasrecord.update", User.Identity.Name));
}
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]

View File

@ -505,7 +505,8 @@ namespace CarCareTracker.Controllers
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Gas Record via CSV Import. {convertedRecord.Notes}"
Notes = $"Auto Insert From Gas Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.GasRecord, convertedRecord.Id, $"Gas Record - {convertedRecord.Mileage.ToString()}")
});
}
}
@ -531,7 +532,8 @@ namespace CarCareTracker.Controllers
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Service Record via CSV Import. {convertedRecord.Notes}"
Notes = $"Auto Insert From Service Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.ServiceRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}
@ -590,7 +592,8 @@ namespace CarCareTracker.Controllers
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Repair Record via CSV Import. {convertedRecord.Notes}"
Notes = $"Auto Insert From Repair Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.RepairRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}
@ -615,7 +618,8 @@ namespace CarCareTracker.Controllers
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record via CSV Import. {convertedRecord.Notes}"
Notes = $"Auto Insert From Upgrade Record via CSV Import. {convertedRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.UpgradeRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}

View File

@ -0,0 +1,230 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetInspectionRecordTemplatesByVehicleId(int vehicleId)
{
var result = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplatesByVehicleId(vehicleId);
return PartialView("Inspection/_InspectionRecordTemplateSelector", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetInspectionRecordsByVehicleId(int vehicleId)
{
var result = _inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(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("Inspection/_InspectionRecords", result);
}
[HttpGet]
public IActionResult GetAddInspectionRecordTemplatePartialView()
{
return PartialView("Inspection/_InspectionRecordTemplateEditModal", new InspectionRecordInput());
}
[HttpGet]
public IActionResult GetEditInspectionRecordTemplatePartialView(int inspectionRecordTemplateId)
{
var result = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
return PartialView("Inspection/_InspectionRecordTemplateEditModal", result);
}
[HttpGet]
public IActionResult GetAddInspectionRecordFieldPartialView()
{
return PartialView("Inspection/_InspectionRecordField", new InspectionRecordTemplateField());
}
public IActionResult GetAddInspectionRecordFieldOptionsPartialView()
{
return PartialView("Inspection/_InspectionRecordFieldOptions", new List<InspectionRecordTemplateFieldOption>());
}
public IActionResult GetAddInspectionRecordFieldOptionPartialView()
{
return PartialView("Inspection/_InspectionRecordFieldOption", new InspectionRecordTemplateFieldOption());
}
[HttpPost]
public IActionResult SaveInspectionRecordTemplateToVehicleId(InspectionRecordInput inspectionRecordTemplate)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), inspectionRecordTemplate.VehicleId))
{
return Json(false);
}
var result = _inspectionRecordTemplateDataAccess.SaveInspectionReportTemplateToVehicle(inspectionRecordTemplate);
return Json(result);
}
private bool DeleteInspectionRecordTemplateWithChecks(int inspectionRecordTemplateId)
{
var existingRecord = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _inspectionRecordTemplateDataAccess.DeleteInspectionRecordTemplateById(existingRecord.Id);
return result;
}
private bool DeleteInspectionRecordWithChecks(int inspectionRecordId)
{
var existingRecord = _inspectionRecordDataAccess.GetInspectionRecordById(inspectionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _inspectionRecordDataAccess.DeleteInspectionRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromInspectionRecord(existingRecord, "inspectionrecord.delete", User.Identity.Name));
}
return result;
}
[HttpPost]
public IActionResult DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
var result = DeleteInspectionRecordTemplateWithChecks(inspectionRecordTemplateId);
return Json(result);
}
[HttpPost]
public IActionResult DeleteInspectionRecordById(int inspectionRecordId)
{
var result = DeleteInspectionRecordWithChecks(inspectionRecordId);
return Json(result);
}
[HttpGet]
public IActionResult GetAddInspectionRecordPartialView(int inspectionRecordTemplateId)
{
var result = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//populate date
result.Date = DateTime.Now.ToShortDateString();
return PartialView("Inspection/_InspectionRecordModal", result);
}
[HttpGet]
public IActionResult GetViewInspectionRecordPartialView(int inspectionRecordId)
{
var result = _inspectionRecordDataAccess.GetInspectionRecordById(inspectionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
return PartialView("Inspection/_InspectionRecordViewModal", result);
}
[HttpPost]
public IActionResult SaveInspectionRecordToVehicleId(InspectionRecordInput inspectionRecord)
{
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), inspectionRecord.VehicleId))
{
return Json(false);
}
//move files from temp.
inspectionRecord.Files = inspectionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
//push back any reminders
if (inspectionRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in inspectionRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(inspectionRecord.Date), inspectionRecord.Mileage);
}
}
var convertedRecord = inspectionRecord.ToInspectionRecord();
var result = _inspectionRecordDataAccess.SaveInspectionRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromInspectionRecord(convertedRecord, "inspectionrecord.add", User.Identity.Name));
}
if (convertedRecord.Id != 0)
{
//insert into service record
List<UploadedFiles> newAttachments = new List<UploadedFiles>();
newAttachments.Add(new UploadedFiles { Name = inspectionRecord.Description, Location = StaticHelper.GetRecordAttachment(ImportMode.InspectionRecord, convertedRecord.Id)});
newAttachments.AddRange(inspectionRecord.Files);
_serviceRecordDataAccess.SaveServiceRecordToVehicle(new ServiceRecord
{
Date = DateTime.Parse(inspectionRecord.Date),
VehicleId = inspectionRecord.VehicleId,
Mileage = inspectionRecord.Mileage,
Description = inspectionRecord.Description,
Cost = inspectionRecord.Cost,
Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}",
Files = newAttachments
});
//auto-insert into odometer if configured
if (inspectionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(inspectionRecord.Date),
VehicleId = inspectionRecord.VehicleId,
Mileage = inspectionRecord.Mileage,
Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.InspectionRecord, convertedRecord.Id, convertedRecord.Description)
});
}
//create action items
var inspectionFieldsWithActionItems = inspectionRecord.Fields.Where(x => x.HasActionItem);
if (inspectionFieldsWithActionItems.Any())
{
foreach (InspectionRecordTemplateField inspectionField in inspectionFieldsWithActionItems)
{
if (inspectionField.ToInspectionRecordResult().Failed)
{
_planRecordDataAccess.SavePlanRecordToVehicle(new PlanRecord
{
DateCreated = DateTime.Now,
DateModified = DateTime.Now,
VehicleId = inspectionRecord.VehicleId,
Description = inspectionField.ActionItemDescription,
ImportMode = inspectionField.ActionItemType,
Priority = inspectionField.ActionItemPriority,
Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.InspectionRecord, convertedRecord.Id, convertedRecord.Description)
});
}
}
}
}
return Json(result);
}
[HttpPost]
public IActionResult UpdateInspectionRecordTags(int inspectionRecordId, List<string> tags)
{
var existingRecord = _inspectionRecordDataAccess.GetInspectionRecordById(inspectionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return Json(false);
}
existingRecord.Tags = tags;
var result = _inspectionRecordDataAccess.SaveInspectionRecordToVehicle(existingRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromInspectionRecord(existingRecord, "inspectionrecord.update", User.Identity.Name));
}
return Json(result);
}
}
}

View File

@ -46,10 +46,11 @@ namespace CarCareTracker.Controllers
}
//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());
var convertedRecord = odometerRecord.ToOdometerRecord();
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord.ToOdometerRecord(), odometerRecord.Id == default ? "odometerrecord.add" : "odometerrecord.update", User.Identity.Name));
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(convertedRecord, odometerRecord.Id == default ? "odometerrecord.add" : "odometerrecord.update", User.Identity.Name));
}
return Json(result);
}

View File

@ -196,17 +196,7 @@ namespace CarCareTracker.Controllers
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
if (planProgress == PlanProgress.Done)
{
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Now.Date,
VehicleId = existingRecord.VehicleId,
Mileage = odometer,
Notes = $"Auto Insert From Plan Record: {existingRecord.Description}",
ExtraFields = existingRecord.ExtraFields
});
}
int newRecordId = 0;
//convert plan record to service/upgrade/repair record.
if (existingRecord.ImportMode == ImportMode.ServiceRecord)
{
@ -223,6 +213,7 @@ namespace CarCareTracker.Controllers
ExtraFields = existingRecord.ExtraFields
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(newRecord);
newRecordId = newRecord.Id;
}
else if (existingRecord.ImportMode == ImportMode.RepairRecord)
{
@ -239,6 +230,7 @@ namespace CarCareTracker.Controllers
ExtraFields = existingRecord.ExtraFields
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(newRecord);
newRecordId = newRecord.Id;
}
else if (existingRecord.ImportMode == ImportMode.UpgradeRecord)
{
@ -255,6 +247,19 @@ namespace CarCareTracker.Controllers
ExtraFields = existingRecord.ExtraFields
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(newRecord);
newRecordId = newRecord.Id;
}
if (newRecordId != default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Now.Date,
VehicleId = existingRecord.VehicleId,
Mileage = odometer,
Notes = $"Auto Insert From Plan Record: {existingRecord.Description}",
ExtraFields = existingRecord.ExtraFields,
Files = StaticHelper.CreateAttachmentFromRecord(existingRecord.ImportMode, newRecordId, existingRecord.Description)
});
}
//push back any reminders
if (existingRecord.ReminderRecordId != default)

View File

@ -82,6 +82,11 @@ namespace CarCareTracker.Controllers
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
if (existingReminder is not null && existingReminder.Id != default && existingReminder.IsRecurring)
{
//security check
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingReminder.VehicleId))
{
return false;
}
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder, currentDate, currentMileage);
//save to db.
var reminderUpdateResult = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);

View File

@ -31,16 +31,6 @@ namespace CarCareTracker.Controllers
{
return Json(false);
}
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(collisionRecord.Date),
VehicleId = collisionRecord.VehicleId,
Mileage = collisionRecord.Mileage,
Notes = $"Auto Insert From Repair Record: {collisionRecord.Description}"
});
}
//move files from temp.
collisionRecord.Files = collisionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (collisionRecord.Supplies.Any())
@ -63,10 +53,22 @@ namespace CarCareTracker.Controllers
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(collisionRecord.Date), collisionRecord.Mileage);
}
}
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
var convertedRecord = collisionRecord.ToCollisionRecord();
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(collisionRecord.ToCollisionRecord(), collisionRecord.Id == default ? "repairrecord.add" : "repairrecord.update", User.Identity.Name));
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(convertedRecord, collisionRecord.Id == default ? "repairrecord.add" : "repairrecord.update", User.Identity.Name));
}
if (convertedRecord.Id != default && collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(collisionRecord.Date),
VehicleId = collisionRecord.VehicleId,
Mileage = collisionRecord.Mileage,
Notes = $"Auto Insert From Repair Record: {collisionRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.RepairRecord, convertedRecord.Id, convertedRecord.Description)
});
}
return Json(result);
}

View File

@ -31,16 +31,6 @@ namespace CarCareTracker.Controllers
{
return Json(false);
}
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(serviceRecord.Date),
VehicleId = serviceRecord.VehicleId,
Mileage = serviceRecord.Mileage,
Notes = $"Auto Insert From Service Record: {serviceRecord.Description}"
});
}
//move files from temp.
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (serviceRecord.Supplies.Any())
@ -63,10 +53,22 @@ namespace CarCareTracker.Controllers
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(serviceRecord.Date), serviceRecord.Mileage);
}
}
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
var convertedRecord = serviceRecord.ToServiceRecord();
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(serviceRecord.ToServiceRecord(), serviceRecord.Id == default ? "servicerecord.add" : "servicerecord.update", User.Identity.Name));
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(convertedRecord, serviceRecord.Id == default ? "servicerecord.add" : "servicerecord.update", User.Identity.Name));
}
if (convertedRecord.Id != default && serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(serviceRecord.Date),
VehicleId = serviceRecord.VehicleId,
Mileage = serviceRecord.Mileage,
Notes = $"Auto Insert From Service Record: {serviceRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.ServiceRecord, convertedRecord.Id, convertedRecord.Description)
});
}
return Json(result);
}

View File

@ -31,16 +31,6 @@ namespace CarCareTracker.Controllers
{
return Json(false);
}
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(upgradeRecord.Date),
VehicleId = upgradeRecord.VehicleId,
Mileage = upgradeRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record: {upgradeRecord.Description}"
});
}
//move files from temp.
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (upgradeRecord.Supplies.Any())
@ -63,10 +53,22 @@ namespace CarCareTracker.Controllers
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Mileage);
}
}
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
var convertedRecord = upgradeRecord.ToUpgradeRecord();
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(upgradeRecord.ToUpgradeRecord(), upgradeRecord.Id == default ? "upgraderecord.add" : "upgraderecord.update", User.Identity.Name));
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(convertedRecord, upgradeRecord.Id == default ? "upgraderecord.add" : "upgraderecord.update", User.Identity.Name));
}
if (convertedRecord != default && upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(upgradeRecord.Date),
VehicleId = upgradeRecord.VehicleId,
Mileage = upgradeRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record: {upgradeRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.UpgradeRecord, convertedRecord.Id, convertedRecord.Description)
});
}
return Json(result);
}

View File

@ -27,6 +27,8 @@ namespace CarCareTracker.Controllers
private readonly IPlanRecordDataAccess _planRecordDataAccess;
private readonly IPlanRecordTemplateDataAccess _planRecordTemplateDataAccess;
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IInspectionRecordDataAccess _inspectionRecordDataAccess;
private readonly IInspectionRecordTemplateDataAccess _inspectionRecordTemplateDataAccess;
private readonly IWebHostEnvironment _webEnv;
private readonly IConfigHelper _config;
private readonly IFileHelper _fileHelper;
@ -60,7 +62,9 @@ namespace CarCareTracker.Controllers
IOdometerLogic odometerLogic,
IVehicleLogic vehicleLogic,
IWebHostEnvironment webEnv,
IConfigHelper config)
IConfigHelper config,
IInspectionRecordDataAccess inspectionRecordDataAccess,
IInspectionRecordTemplateDataAccess inspectionRecordTemplateDataAccess)
{
_logger = logger;
_dataAccess = dataAccess;
@ -78,6 +82,8 @@ namespace CarCareTracker.Controllers
_supplyRecordDataAccess = supplyRecordDataAccess;
_planRecordDataAccess = planRecordDataAccess;
_planRecordTemplateDataAccess = planRecordTemplateDataAccess;
_inspectionRecordDataAccess = inspectionRecordDataAccess;
_inspectionRecordTemplateDataAccess = inspectionRecordTemplateDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess;
_extraFieldDataAccess = extraFieldDataAccess;
_userLogic = userLogic;
@ -159,6 +165,8 @@ namespace CarCareTracker.Controllers
_upgradeRecordDataAccess.DeleteAllUpgradeRecordsByVehicleId(vehicleId) &&
_planRecordDataAccess.DeleteAllPlanRecordsByVehicleId(vehicleId) &&
_planRecordTemplateDataAccess.DeleteAllPlanRecordTemplatesByVehicleId(vehicleId) &&
_inspectionRecordDataAccess.DeleteAllInspectionRecordsByVehicleId(vehicleId) &&
_inspectionRecordTemplateDataAccess.DeleteAllInspectionReportTemplatesByVehicleId(vehicleId) &&
_supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) &&
_odometerRecordDataAccess.DeleteAllOdometerRecordsByVehicleId(vehicleId) &&
_userLogic.DeleteAllAccessToVehicle(vehicleId) &&
@ -187,6 +195,8 @@ namespace CarCareTracker.Controllers
_upgradeRecordDataAccess.DeleteAllUpgradeRecordsByVehicleId(vehicleId) &&
_planRecordDataAccess.DeleteAllPlanRecordsByVehicleId(vehicleId) &&
_planRecordTemplateDataAccess.DeleteAllPlanRecordTemplatesByVehicleId(vehicleId) &&
_inspectionRecordDataAccess.DeleteAllInspectionRecordsByVehicleId(vehicleId) &&
_inspectionRecordTemplateDataAccess.DeleteAllInspectionReportTemplatesByVehicleId(vehicleId) &&
_supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) &&
_odometerRecordDataAccess.DeleteAllOdometerRecordsByVehicleId(vehicleId) &&
_userLogic.DeleteAllAccessToVehicle(vehicleId) &&
@ -455,6 +465,19 @@ namespace CarCareTracker.Controllers
}
}
break;
case ImportMode.InspectionRecord:
{
var results = _inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(vehicleId);
if (caseSensitive)
{
searchResults.AddRange(results.Where(x => JsonSerializer.Serialize(x).Contains(searchQuery)).Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.InspectionRecord, Description = $"{x.Date.ToShortDateString()} - {x.Description}" }));
}
else
{
searchResults.AddRange(results.Where(x => JsonSerializer.Serialize(x).ToLower().Contains(searchQuery)).Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.InspectionRecord, Description = $"{x.Date.ToShortDateString()} - {x.Description}" }));
}
}
break;
}
}
return PartialView("_GlobalSearchResult", searchResults);
@ -536,10 +559,85 @@ namespace CarCareTracker.Controllers
searchResults.AddRange(results.Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.ReminderRecord, Description = $"{x.Description}" }));
}
break;
case ImportMode.InspectionRecord:
{
var results = _inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(vehicleId);
results.RemoveAll(x => !x.Tags.Any(y => tagsFilter.Contains(y)));
searchResults.AddRange(results.Select(x => new SearchResult { Id = x.Id, RecordType = ImportMode.InspectionRecord, Description = $"{x.Date.ToShortDateString()} - {x.Description}" }));
}
break;
}
}
return PartialView("_MapSearchResult", searchResults);
}
[HttpPost]
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult CheckRecordExist(int vehicleId, ImportMode importMode, int recordId)
{
if (recordId == default)
{
return Json(OperationResponse.Failed("Invalid Record"));
}
switch (importMode)
{
case ImportMode.ServiceRecord:
{
var results = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Service Record Not Found"));
}
case ImportMode.RepairRecord:
{
var results = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Repair Record Not Found"));
}
case ImportMode.UpgradeRecord:
{
var results = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Upgrade Record Not Found"));
}
case ImportMode.TaxRecord:
{
var results = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Tax Record Not Found"));
}
case ImportMode.SupplyRecord:
{
var results = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Supply Record Not Found"));
}
case ImportMode.PlanRecord:
{
var results = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Plan Record Not Found"));
}
case ImportMode.OdometerRecord:
{
var results = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Odometer Record Not Found"));
}
case ImportMode.GasRecord:
{
var results = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Gas Record Not Found"));
}
case ImportMode.NoteRecord:
{
var results = _noteDataAccess.GetNotesByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Note Record Not Found"));
}
case ImportMode.ReminderRecord:
{
var results = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Reminder Not Found"));
}
case ImportMode.InspectionRecord:
{
var results = _inspectionRecordDataAccess.GetInspectionRecordsByVehicleId(vehicleId);
return Json(OperationResponse.Conditional(results.Any(x => x.Id == recordId), "", "Inspection Record Not Found"));
}
}
return Json(OperationResponse.Failed("Record Not Found"));
}
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult GetMaxMileage(int vehicleId)
{
@ -683,6 +781,9 @@ namespace CarCareTracker.Controllers
case ImportMode.ReminderRecord:
result = DeleteReminderRecordWithChecks(recordId);
break;
case ImportMode.InspectionRecord:
result = DeleteInspectionRecordWithChecks(recordId);
break;
}
}
if (result)
@ -969,6 +1070,18 @@ namespace CarCareTracker.Controllers
}
}
break;
case ImportMode.InspectionRecord:
{
var existingRecord = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(recordId);
existingRecord.Id = default;
existingRecord.ReminderRecordId = new List<int>();
foreach (int vehicleId in vehicleIds)
{
existingRecord.VehicleId = vehicleId;
result = _inspectionRecordTemplateDataAccess.SaveInspectionReportTemplateToVehicle(existingRecord);
}
}
break;
}
}
if (result)
@ -993,7 +1106,8 @@ namespace CarCareTracker.Controllers
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Service Record: {existingRecord.Description}"
Notes = $"Auto Insert From Service Record: {existingRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(importMode, existingRecord.Id, existingRecord.Description)
});
}
break;
@ -1005,7 +1119,8 @@ namespace CarCareTracker.Controllers
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Repair Record: {existingRecord.Description}"
Notes = $"Auto Insert From Repair Record: {existingRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(importMode, existingRecord.Id, existingRecord.Description)
});
}
break;
@ -1017,7 +1132,8 @@ namespace CarCareTracker.Controllers
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record: {existingRecord.Description}"
Notes = $"Auto Insert From Upgrade Record: {existingRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(importMode, existingRecord.Id, existingRecord.Description)
});
}
break;
@ -1029,7 +1145,8 @@ namespace CarCareTracker.Controllers
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Gas Record. {existingRecord.Notes}"
Notes = $"Auto Insert From Gas Record. {existingRecord.Notes}",
Files = StaticHelper.CreateAttachmentFromRecord(importMode, existingRecord.Id, $"Gas Record - {existingRecord.Mileage.ToString()}")
});
}
break;
@ -1367,6 +1484,14 @@ namespace CarCareTracker.Controllers
}
}
break;
case ImportMode.InspectionRecord:
foreach (int recordId in recordIds)
{
var record = _inspectionRecordDataAccess.GetInspectionRecordById(recordId);
stickerViewModel.InspectionRecords.Add(record);
recordsAdded++;
}
break;
}
if (recordsAdded > 0)
{

View File

@ -13,6 +13,7 @@
Dashboard = 8,
PlanRecord = 9,
OdometerRecord = 10,
VehicleRecord = 11
VehicleRecord = 11,
InspectionRecord = 12
}
}

View File

@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public enum InspectionFieldType
{
Text = 0,
Check = 1,
Radio = 2
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class InspectionRecordDataAccess : IInspectionRecordDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "inspectionrecords";
public InspectionRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<InspectionRecord> GetInspectionRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
var inspectionRecords = table.Find(Query.EQ(nameof(InspectionRecord.VehicleId), vehicleId));
return inspectionRecords.ToList() ?? new List<InspectionRecord>();
}
public InspectionRecord GetInspectionRecordById(int inspectionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
return table.FindById(inspectionRecordId);
}
public bool DeleteInspectionRecordById(int inspectionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
table.Delete(inspectionRecordId);
db.Checkpoint();
return true;
}
public bool SaveInspectionRecordToVehicle(InspectionRecord inspectionRecordId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
table.Upsert(inspectionRecordId);
db.Checkpoint();
return true;
}
public bool DeleteAllInspectionRecordsByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecord>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(InspectionRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class InspectionRecordTemplateDataAccess : IInspectionRecordTemplateDataAccess
{
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "inspectionrecordtemplates";
public InspectionRecordTemplateDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<InspectionRecordInput> GetInspectionRecordTemplatesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
var inspectionRecords = table.Find(Query.EQ(nameof(InspectionRecordInput.VehicleId), vehicleId));
return inspectionRecords.ToList() ?? new List<InspectionRecordInput>();
}
public InspectionRecordInput GetInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
return table.FindById(inspectionRecordTemplateId);
}
public bool DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
table.Delete(inspectionRecordTemplateId);
db.Checkpoint();
return true;
}
public bool SaveInspectionReportTemplateToVehicle(InspectionRecordInput inspectionRecordTemplate)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
table.Upsert(inspectionRecordTemplate);
db.Checkpoint();
return true;
}
public bool DeleteAllInspectionReportTemplatesByVehicleId(int vehicleId)
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<InspectionRecordInput>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(InspectionRecordInput.VehicleId), vehicleId));
db.Checkpoint();
return true;
}
}
}

View File

@ -0,0 +1,160 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
using System.Text.Json;
namespace CarCareTracker.External.Implementations
{
public class PGInspectionRecordDataAccess : IInspectionRecordDataAccess
{
private NpgsqlDataSource pgDataSource;
private readonly ILogger<PGInspectionRecordDataAccess> _logger;
private static string tableName = "inspectionrecords";
public PGInspectionRecordDataAccess(IConfiguration config, ILogger<PGInspectionRecordDataAccess> logger)
{
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
_logger = logger;
try
{
//create table if not exist.
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
public List<InspectionRecord> GetInspectionRecordsByVehicleId(int vehicleId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
var results = new List<InspectionRecord>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecord inspectionRecord = JsonSerializer.Deserialize<InspectionRecord>(reader["data"] as string);
results.Add(inspectionRecord);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<InspectionRecord>();
}
}
public InspectionRecord GetInspectionRecordById(int inspectionRecordId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
var result = new InspectionRecord();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecord inspectionRecord = JsonSerializer.Deserialize<InspectionRecord>(reader["data"] as string);
result = inspectionRecord;
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new InspectionRecord();
}
}
public bool DeleteInspectionRecordById(int inspectionRecordId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordId);
return ctext.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool SaveInspectionRecordToVehicle(InspectionRecord inspectionRecordId)
{
try
{
if (inspectionRecordId.Id == default)
{
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", inspectionRecordId.VehicleId);
ctext.Parameters.AddWithValue("data", "{}");
inspectionRecordId.Id = Convert.ToInt32(ctext.ExecuteScalar());
//update json data
if (inspectionRecordId.Id != default)
{
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
using (var ctextU = pgDataSource.CreateCommand(cmdU))
{
var serializedData = JsonSerializer.Serialize(inspectionRecordId);
ctextU.Parameters.AddWithValue("id", inspectionRecordId.Id);
ctextU.Parameters.AddWithValue("data", serializedData);
return ctextU.ExecuteNonQuery() > 0;
}
}
return inspectionRecordId.Id != default;
}
}
else
{
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
var serializedData = JsonSerializer.Serialize(inspectionRecordId);
ctext.Parameters.AddWithValue("id", inspectionRecordId.Id);
ctext.Parameters.AddWithValue("data", serializedData);
return ctext.ExecuteNonQuery() > 0;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteAllInspectionRecordsByVehicleId(int vehicleId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,160 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
using System.Text.Json;
namespace CarCareTracker.External.Implementations
{
public class PGInspectionRecordTemplateDataAccess : IInspectionRecordTemplateDataAccess
{
private NpgsqlDataSource pgDataSource;
private readonly ILogger<PGInspectionRecordTemplateDataAccess> _logger;
private static string tableName = "inspectionrecordtemplates";
public PGInspectionRecordTemplateDataAccess(IConfiguration config, ILogger<PGInspectionRecordTemplateDataAccess> logger)
{
pgDataSource = NpgsqlDataSource.Create(config["POSTGRES_CONNECTION"]);
_logger = logger;
try
{
//create table if not exist.
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
public List<InspectionRecordInput> GetInspectionRecordTemplatesByVehicleId(int vehicleId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE vehicleId = @vehicleId";
var results = new List<InspectionRecordInput>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecordInput inspectionRecord = JsonSerializer.Deserialize<InspectionRecordInput>(reader["data"] as string);
results.Add(inspectionRecord);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<InspectionRecordInput>();
}
}
public InspectionRecordInput GetInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
try
{
string cmd = $"SELECT data FROM app.{tableName} WHERE id = @id";
var result = new InspectionRecordInput();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordTemplateId);
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
InspectionRecordInput inspectionRecord = JsonSerializer.Deserialize<InspectionRecordInput>(reader["data"] as string);
result = inspectionRecord;
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new InspectionRecordInput();
}
}
public bool DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", inspectionRecordTemplateId);
return ctext.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool SaveInspectionReportTemplateToVehicle(InspectionRecordInput inspectionRecordTemplate)
{
try
{
if (inspectionRecordTemplate.Id == default)
{
string cmd = $"INSERT INTO app.{tableName} (vehicleId, data) VALUES(@vehicleId, CAST(@data AS jsonb)) RETURNING id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", inspectionRecordTemplate.VehicleId);
ctext.Parameters.AddWithValue("data", "{}");
inspectionRecordTemplate.Id = Convert.ToInt32(ctext.ExecuteScalar());
//update json data
if (inspectionRecordTemplate.Id != default)
{
string cmdU = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
using (var ctextU = pgDataSource.CreateCommand(cmdU))
{
var serializedData = JsonSerializer.Serialize(inspectionRecordTemplate);
ctextU.Parameters.AddWithValue("id", inspectionRecordTemplate.Id);
ctextU.Parameters.AddWithValue("data", serializedData);
return ctextU.ExecuteNonQuery() > 0;
}
}
return inspectionRecordTemplate.Id != default;
}
}
else
{
string cmd = $"UPDATE app.{tableName} SET data = CAST(@data AS jsonb) WHERE id = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
var serializedData = JsonSerializer.Serialize(inspectionRecordTemplate);
ctext.Parameters.AddWithValue("id", inspectionRecordTemplate.Id);
ctext.Parameters.AddWithValue("data", serializedData);
return ctext.ExecuteNonQuery() > 0;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteAllInspectionReportTemplatesByVehicleId(int vehicleId)
{
try
{
string cmd = $"DELETE FROM app.{tableName} WHERE vehicleId = @id";
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IInspectionRecordDataAccess
{
public List<InspectionRecord> GetInspectionRecordsByVehicleId(int vehicleId);
public InspectionRecord GetInspectionRecordById(int inspectionRecordId);
public bool DeleteInspectionRecordById(int inspectionRecordId);
public bool SaveInspectionRecordToVehicle(InspectionRecord inspectionRecordId);
public bool DeleteAllInspectionRecordsByVehicleId(int vehicleId);
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface IInspectionRecordTemplateDataAccess
{
public List<InspectionRecordInput> GetInspectionRecordTemplatesByVehicleId(int vehicleId);
public InspectionRecordInput GetInspectionRecordTemplateById(int inspectionRecordTemplateId);
public bool DeleteInspectionRecordTemplateById(int inspectionRecordTemplateId);
public bool SaveInspectionReportTemplateToVehicle(InspectionRecordInput inspectionRecordTemplate);
public bool DeleteAllInspectionReportTemplatesByVehicleId(int vehicleId);
}
}

View File

@ -24,6 +24,7 @@ namespace CarCareTracker.Helper
bool WidgetsExist();
bool SaveWidgets(string widgetsData);
bool DeleteWidgets();
string WriteFileToTemp(string fileName, string data);
}
public class FileHelper : IFileHelper
{
@ -514,5 +515,25 @@ namespace CarCareTracker.Helper
return false;
}
}
public string WriteFileToTemp(string fileName, string data)
{
try
{
//check if temp folder exists
var tempFolder = GetFullFilePath("/temp", false);
if (!Directory.Exists(tempFolder))
{
Directory.CreateDirectory(tempFolder);
}
var tempFileName = $"/temp/{fileName}";
var fullTempFileName = GetFullFilePath(tempFileName, false);
File.WriteAllText(fullTempFileName, data);
return tempFileName;
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return string.Empty;
}
}
}
}

View File

@ -12,7 +12,7 @@ namespace CarCareTracker.Helper
/// </summary>
public static class StaticHelper
{
public const string VersionNumber = "1.5.3";
public const string VersionNumber = "1.5.4";
public const string DbName = "data/cartracker.db";
public const string UserConfigPath = "data/config/userConfig.json";
public const string ServerConfigPath = "data/config/serverConfig.json";
@ -449,6 +449,8 @@ namespace CarCareTracker.Helper
return "bi-journal-bookmark";
case ImportMode.ReminderRecord:
return "bi-bell";
case ImportMode.InspectionRecord:
return "bi-clipboard-check";
default:
return "bi-file-bar-graph";
}
@ -755,10 +757,46 @@ namespace CarCareTracker.Helper
var imageExtensions = new[] { ".png", ".jpg", ".jpeg" };
return imageExtensions.Contains(fileExt);
}
public static bool GetAttachmentIsRecord(string fileLocation, out ImportMode importMode, out int recordId)
{
var urlMatchesPattern = fileLocation.StartsWith("::") && fileLocation.Split(':').Length == 4;
importMode = ImportMode.Dashboard;
recordId = 0;
if (urlMatchesPattern)
{
var recordType = fileLocation.Split(':')[2];
var recordTypeIsEnum = Enum.TryParse(recordType, out ImportMode recordMode);
if (recordTypeIsEnum)
{
importMode = recordMode;
recordId = int.Parse(fileLocation.Split(':')[3]);
}
}
return urlMatchesPattern;
}
public static string GetRecordAttachment(ImportMode importMode, int recordId)
{
return $"::{importMode.ToString()}:{recordId.ToString()}";
}
public static List<UploadedFiles> CreateAttachmentFromRecord(ImportMode importMode, int recordId, string description)
{
return new List<UploadedFiles>
{
new UploadedFiles
{
Name = description,
Location = GetRecordAttachment(importMode, recordId)
}
};
}
public static string GetIconByFileExtension(string fileLocation)
{
var fileExt = Path.GetExtension(fileLocation);
if (GetAttachmentIsLink(fileLocation))
if (GetAttachmentIsRecord(fileLocation, out ImportMode recordType, out int recordId))
{
return GetImportModeIcon(recordType);
}
else if (GetAttachmentIsLink(fileLocation))
{
return "bi-link-45deg";
}

View File

@ -9,7 +9,6 @@ namespace CarCareTracker.Helper
string Translate(string userLanguage, string text);
Dictionary<string, string> GetTranslations(string userLanguage);
OperationResponse SaveTranslation(string userLanguage, Dictionary<string, string> translations);
string ExportTranslation(Dictionary<string, string> translations);
}
public class TranslationHelper : ITranslationHelper
{
@ -167,31 +166,5 @@ namespace CarCareTracker.Helper
return OperationResponse.Failed();
}
}
public string ExportTranslation(Dictionary<string, string> translations)
{
try
{
var tempFileName = $"/temp/{Guid.NewGuid()}.json";
string uploadDirectory = _fileHelper.GetFullFilePath("temp/", false);
if (!Directory.Exists(uploadDirectory))
{
Directory.CreateDirectory(uploadDirectory);
}
var saveFilePath = _fileHelper.GetFullFilePath(tempFileName, false);
//standardize translation format for export only.
Dictionary<string, string> sortedTranslations = new Dictionary<string, string>();
foreach (var translation in translations.OrderBy(x => x.Key))
{
sortedTranslations.Add(translation.Key, translation.Value);
};
File.WriteAllText(saveFilePath, JsonSerializer.Serialize(sortedTranslations, new JsonSerializerOptions { WriteIndented = true }));
return tempFileName;
}
catch(Exception ex)
{
_logger.LogError(ex.Message);
return string.Empty;
}
}
}
}

View File

@ -0,0 +1,16 @@
namespace CarCareTracker.Models
{
public class InspectionRecord
{
public int Id { get; set; }
public int VehicleId { get; set; }
public DateTime Date { get; set; }
public int Mileage { get; set; }
public decimal Cost { get; set; }
public string Description { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>();
public List<InspectionRecordResult> Results { get; set; } = new List<InspectionRecordResult>();
public bool Failed { get { return Results.Any(x => x.Failed); } }
}
}

View File

@ -0,0 +1,31 @@
namespace CarCareTracker.Models
{
public class InspectionRecordInput
{
public int Id { get; set; }
public int VehicleId { get; set; }
public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int Mileage { get; set; }
public decimal Cost { get; set; }
public string Description { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>();
public List<InspectionRecordTemplateField> Fields { get; set; } = new List<InspectionRecordTemplateField>();
public InspectionRecord ToInspectionRecord()
{
return new InspectionRecord
{
Id = Id,
VehicleId = VehicleId,
Date = DateTime.Parse(Date),
Cost = Cost,
Mileage = Mileage,
Description = Description,
Results = Fields.Select(x => x.ToInspectionRecordResult()).ToList(),
Files = Files,
Tags = Tags
};
}
}
}

View File

@ -0,0 +1,46 @@
namespace CarCareTracker.Models
{
public class InspectionRecordTemplateField
{
public string Description { get; set; }
public InspectionFieldType FieldType { get; set; } = InspectionFieldType.Text;
public List<InspectionRecordTemplateFieldOption> Options { get; set; } = new List<InspectionRecordTemplateFieldOption>();
public ImportMode ActionItemType { get; set; } = ImportMode.ServiceRecord;
public string ActionItemDescription { get; set; }
public PlanPriority ActionItemPriority { get; set; } = PlanPriority.Normal;
public bool HasNotes { get; set; }
public bool HasActionItem { get; set; }
public string Notes { get; set; }
public InspectionRecordResult ToInspectionRecordResult()
{
return Options.Any() ? new InspectionRecordResult
{
Description = Description,
FieldType = FieldType,
Values = Options.Select(x => new InspectionRecordResultValue{Description = x.Description, IsSelected = x.IsSelected, IsFail = x.IsFail }).ToList(),
Failed = (FieldType == InspectionFieldType.Radio && Options.Any(x => x.IsSelected && x.IsFail)) || (FieldType == InspectionFieldType.Check && Options.Any(x=> !x.IsSelected && x.IsFail)),
Notes = HasNotes ? Notes : string.Empty
} : new InspectionRecordResult();
}
}
public class InspectionRecordTemplateFieldOption
{
public string Description { get; set; }
public bool IsFail { get; set; }
public bool IsSelected { get; set; }
}
public class InspectionRecordResult
{
public string Description { get; set; }
public List<InspectionRecordResultValue> Values { get; set; } = new List<InspectionRecordResultValue>();
public bool Failed { get; set; }
public string Notes { get; set; }
public InspectionFieldType FieldType { get; set; } = InspectionFieldType.Text;
}
public class InspectionRecordResultValue
{
public string Description { get; set; }
public bool IsSelected { get; set; }
public bool IsFail { get; set; }
}
}

View File

@ -7,5 +7,6 @@
public List<ReminderRecord> ReminderRecords { get; set; } = new List<ReminderRecord>();
public List<GenericRecord> GenericRecords { get; set; } = new List<GenericRecord>();
public List<SupplyRecord> SupplyRecords { get; set; } = new List<SupplyRecord>();
public List<InspectionRecord> InspectionRecords { get; set; } = new List<InspectionRecord>();
}
}

View File

@ -184,6 +184,22 @@ namespace CarCareTracker.Models
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {planRecord.Description}"
};
}
public static WebHookPayload FromInspectionRecord(InspectionRecord inspectionRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", inspectionRecord.Description);
payloadDictionary.Add("vehicleId", inspectionRecord.VehicleId.ToString());
payloadDictionary.Add("cost", inspectionRecord.Cost.ToString("F2"));
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = inspectionRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {inspectionRecord.Description}"
};
}
public static WebHookPayload FromReminderRecord(ReminderRecord reminderRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();

View File

@ -51,6 +51,7 @@
ImportMode.SupplyRecord,
ImportMode.TaxRecord,
ImportMode.NoteRecord,
ImportMode.InspectionRecord,
ImportMode.ReminderRecord
};
}

View File

@ -58,6 +58,8 @@ if (!string.IsNullOrWhiteSpace(builder.Configuration["POSTGRES_CONNECTION"])){
builder.Services.AddSingleton<ITokenRecordDataAccess, PGTokenRecordDataAccess>();
builder.Services.AddSingleton<IUserAccessDataAccess, PGUserAccessDataAccess>();
builder.Services.AddSingleton<IExtraFieldDataAccess, PGExtraFieldDataAccess>();
builder.Services.AddSingleton<IInspectionRecordDataAccess, PGInspectionRecordDataAccess>();
builder.Services.AddSingleton<IInspectionRecordTemplateDataAccess, PGInspectionRecordTemplateDataAccess>();
}
else
{
@ -78,6 +80,8 @@ else
builder.Services.AddSingleton<ITokenRecordDataAccess, TokenRecordDataAccess>();
builder.Services.AddSingleton<IUserAccessDataAccess, UserAccessDataAccess>();
builder.Services.AddSingleton<IExtraFieldDataAccess, ExtraFieldDataAccess>();
builder.Services.AddSingleton<IInspectionRecordDataAccess, InspectionRecordDataAccess>();
builder.Services.AddSingleton<IInspectionRecordTemplateDataAccess, InspectionRecordTemplateDataAccess>();
}
//configure helpers

View File

@ -5,9 +5,16 @@
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var attachmentIsRecord = StaticHelper.GetAttachmentIsRecord(Model.Location, out ImportMode importMode, out int recordId);
}
<div class="modal-header">
@if (StaticHelper.GetAttachmentIsLink(Model.Location))
@if (attachmentIsRecord)
{
<a href="#" onclick="loadRecordAttachment(@recordId, '@importMode')">
<span class="link-body-emphasis"><i class="bi @StaticHelper.GetImportModeIcon(importMode)"></i></span>
</a>
}
else if (StaticHelper.GetAttachmentIsLink(Model.Location))
{
<a href="@Model.Location" target="_blank">
<span class="link-body-emphasis"><i class="bi bi-box-arrow-up-right"></i></span>
@ -23,7 +30,13 @@
</div>
<div class="modal-body">
<div class="d-flex justify-content-center" style="height:100%; width:100%;" onclick="closeAttachmentPreview()">
@if (StaticHelper.GetAttachmentIsLink(Model.Location))
@if (attachmentIsRecord)
{
<div class="align-self-center">
<button onclick="loadRecordAttachment(@recordId, '@importMode')" class="btn btn-primary"><span class="me-2">@translator.Translate(userLanguage, "Go To Record")</span><span><i class="bi @StaticHelper.GetImportModeIcon(importMode)"></i></span></button>
</div>
}
else if (StaticHelper.GetAttachmentIsLink(Model.Location))
{
<div class="align-self-center">
<a href="@Model.Location" target="_blank" onclick="noPropagation()" class="btn btn-primary"><span class="me-2">@translator.Translate(userLanguage, "Open Link in New Tab")</span><span><i class="bi bi-box-arrow-up-right"></i></span></a>

View File

@ -123,6 +123,7 @@
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'supply')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Supplies")</span><i class="bi bi-shop"></i></div></a></li>
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'tax')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Taxes")</span><i class="bi bi-currency-dollar"></i></div></a></li>
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'notes')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Notes")</span><i class="bi bi-journal-bookmark"></i></div></a></li>
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.InspectionRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'inspection')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Inspections")</span><i class="bi bi-clipboard-check"></i></div></a></li>
<li><a class="context-menu-active-single dropdown-item @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" href="#" onclick="viewVehicleWithTab(selectedVehicles, 'reminder')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Reminders")</span><i class="bi bi-bell"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteVehicles(selectedVehicles)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>

View File

@ -132,13 +132,13 @@
<div class="col-12 col-md-6">
<ul class="list-group">
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ServiceRecord" id="serviceRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.ServiceRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="serviceRecordTab">@translator.Translate(userLanguage, "Service Records")</label>
</li>
<li class="list-group-item d-none">
<input onChange="updateSettings()" disabled class="form-check-input me-1" type="checkbox" value="Dashboard" id="dashboardTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.Dashboard) ? "checked" : "")>
<label class="form-check-label stretched-link" for="dashboardTab">@translator.Translate(userLanguage, "Dashboard")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ServiceRecord" id="serviceRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.ServiceRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="serviceRecordTab">@translator.Translate(userLanguage, "Service Records")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="RepairRecord" id="repairRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.RepairRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="repairRecordTab">@translator.Translate(userLanguage, "Repairs")</label>
@ -179,6 +179,10 @@
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="PlanRecord" id="planRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.PlanRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="planRecordTab">@translator.Translate(userLanguage, "Planner")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="InspectionRecord" id="inspectionRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.InspectionRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="inspectionRecordTab">@translator.Translate(userLanguage, "Inspections")</label>
</li>
</ul>
</div>
</div>
@ -197,6 +201,7 @@
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.SupplyRecord)) value="SupplyRecord">@translator.Translate(userLanguage, "Supplies")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.PlanRecord)) value="PlanRecord">@translator.Translate(userLanguage, "Planner")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.OdometerRecord)) value="OdometerRecord">@translator.Translate(userLanguage, "Odometer")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.InspectionRecord)) value="InspectionRecord">@translator.Translate(userLanguage, "Inspections")</!option>
</select>
</div>
<div class="col-12 col-md-6">
@ -378,6 +383,7 @@
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.SupplyRecord)" draggable="true" data-tab="@ImportMode.SupplyRecord">@translator.Translate(userLanguage, "Supplies")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.TaxRecord)" draggable="true" data-tab="@ImportMode.TaxRecord">@translator.Translate(userLanguage, "Taxes")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)" draggable="true" data-tab="@ImportMode.NoteRecord">@translator.Translate(userLanguage, "Notes")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x => x == ImportMode.InspectionRecord)" draggable="true" data-tab="@ImportMode.InspectionRecord">@translator.Translate(userLanguage, "Inspections")</li>
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)" draggable="true" data-tab="@ImportMode.ReminderRecord">@translator.Translate(userLanguage, "Reminder")</li>
</ul>
</div>

View File

@ -1,8 +1,18 @@
<div class="row">
<div class="col-1">
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
</div>
<div class="col-11">
<h1>Access Denied</h1>
</div>
</div>
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
}
@{
ViewData["Title"] = "Access Denied";
}
<div class="no-top-pad d-flex align-items-center justify-content-center" style="height:100vh;">
<div class="flex-column d-flex">
<span class="display-1">@translator.Translate(userLanguage, "Access Denied")</span>
<hr />
<div class="d-flex justify-content-center">
<a href="/Home" class="btn btn-outline-secondary"><i class="bi bi-car-front me-2"></i>@translator.Translate(userLanguage, "Return to Garage")</a>
</div>
</div>
</div>

View File

@ -22,6 +22,7 @@
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/planrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/odometerrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/inspectionrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
@ -67,6 +68,9 @@
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark"></i><span class="ms-2">@translator.Translate(userLanguage, "Notes")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x => x == ImportMode.InspectionRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.InspectionRecord)" id="inspection-tab" data-bs-toggle="tab" data-bs-target="#inspection-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-clipboard-check"></i><span class="ms-2">@translator.Translate(userLanguage, "Inspections")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell"></i></div><span class="ms-2">@translator.Translate(userLanguage, "Reminders")</span></button>
</li>
@ -129,6 +133,9 @@
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-journal-bookmark me-2"></i>@translator.Translate(userLanguage, "Notes")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x => x == ImportMode.InspectionRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.InspectionRecord)" id="inspection-tab" data-bs-toggle="tab" data-bs-target="#inspection-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-clipboard-check me-2"></i>@translator.Translate(userLanguage, "Inspections")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>@translator.Translate(userLanguage, "Reminders")</span></button>
</li>
@ -147,6 +154,7 @@
<div class="tab-pane fade" id="supply-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="plan-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="odometer-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="inspection-tab-pane" role="tabpanel" tabindex="0"></div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="editVehicleModal" tabindex="-1" role="dialog" aria-hidden="true">

View File

@ -0,0 +1,63 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model InspectionRecordTemplateField
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div data-type="field">
<label>@translator.Translate(userLanguage, "Description")</label>
<div class="d-flex justify-content-between align-items-center">
<input type="text" data-type="fieldDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Description of Inspected Item")" value="@Model.Description">
<button type="button" class="btn btn-outline-secondary btn-sm ms-2" onclick="moveInspectionRecordField(this)"><i class="bi bi-arrow-up"></i></button>
<button type="button" class="btn btn-outline-secondary btn-sm ms-2" onclick="moveInspectionRecordField(this, true)"><i class="bi bi-arrow-down"></i></button>
<button type="button" class="btn btn-outline-secondary btn-sm ms-2" onclick="duplicateInspectionRecordField(this)"><i class="bi bi-copy"></i></button>
<button type="button" class="btn btn-outline-danger btn-sm ms-2" onclick="deleteInspectionRecordField(this)"><i class="bi bi-trash"></i></button>
</div>
<label>@translator.Translate(userLanguage, "Type")</label>
<select class="form-select" data-type="fieldType" onchange="handleInspectionRecordFieldTypeChange(this)">
<!option value="Text" @(Model.FieldType == InspectionFieldType.Text ? "selected" : "")>@translator.Translate(userLanguage, "Text")</!option>
<!option value="Check" @(Model.FieldType == InspectionFieldType.Check ? "selected" : "")>@translator.Translate(userLanguage, "Check")</!option>
<!option value="Radio" @(Model.FieldType == InspectionFieldType.Radio ? "selected" : "")>@translator.Translate(userLanguage, "Radio")</!option>
</select>
<div data-type="fieldOptions">
@if (Model.Options.Any())
{
@switch (Model.FieldType)
{
case InspectionFieldType.Radio:
case InspectionFieldType.Check:
@await Html.PartialAsync("Inspection/_InspectionRecordFieldOptions", Model.Options)
break;
}
}
</div>
<div data-type="fieldActionItem" style="@(Model.FieldType == InspectionFieldType.Text ? "display:none;" : "")">
<div class="form-check form-check-inline">
<input class="form-check-input" data-type="fieldHasNotes" type="checkbox" role="switch" @(Model.HasNotes ? "checked" : "")>
<label class="form-check-label" onclick="stretchedLinkClick(this)">@translator.Translate(userLanguage, "Has Notes")</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" data-type="fieldHasActionItem" type="checkbox" role="switch" onchange="handleInspectionRecordFieldHasActionItemChange(this)" @(Model.HasActionItem ? "checked" : "")>
<label class="form-check-label" onclick="stretchedLinkClick(this)">@translator.Translate(userLanguage, "Has Action Item")</label>
</div>
<div class="collapse @(Model.HasActionItem ? "show" : "")" data-type="fieldActionItemContainer">
<label>@translator.Translate(userLanguage, "Action Item Description")</label>
<input type="text" data-type="fieldActionItemDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Description of Action Item")" value="@Model.ActionItemDescription">
<label>@translator.Translate(userLanguage, "Action Item Type")</label>
<select class="form-select" data-type="fieldActionItemType" onchange="setDropDownOptionSelected(this)">
<!option value="ServiceRecord" @(Model.ActionItemType == ImportMode.ServiceRecord ? "selected" : "")>@translator.Translate(userLanguage, "Service")</!option>
<!option value="RepairRecord" @(Model.ActionItemType == ImportMode.RepairRecord ? "selected" : "")>@translator.Translate(userLanguage, "Repair")</!option>
<!option value="UpgradeRecord" @(Model.ActionItemType == ImportMode.UpgradeRecord ? "selected" : "")>@translator.Translate(userLanguage, "Upgrade")</!option>
</select>
<label>@translator.Translate(userLanguage, "Action Item Priority")</label>
<select class="form-select" data-type="fieldActionItemPriority" onchange="setDropDownOptionSelected(this)">
<!option value="Critical" @(Model.ActionItemPriority == PlanPriority.Critical ? "selected" : "")>@translator.Translate(userLanguage, "Critical")</!option>
<!option value="Normal" @(Model.ActionItemPriority == PlanPriority.Normal ? "selected" : "")>@translator.Translate(userLanguage, "Normal")</!option>
<!option value="Low" @(Model.ActionItemPriority == PlanPriority.Low ? "selected" : "")>@translator.Translate(userLanguage, "Low")</!option>
</select>
</div>
</div>
<hr />
</div>

View File

@ -0,0 +1,16 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model InspectionRecordTemplateFieldOption
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div data-type="fieldOption" class="d-flex justify-content-between align-items-center mt-2">
<input type="text" data-type="fieldOptionText" class="form-control" value="@Model.Description">
<div class="ms-2 form-check form-check-inline">
<input class="form-check-input" data-type="fieldOptionIsFail" type="checkbox" role="switch" @(Model.IsFail ? "checked" : "")>
<label class="form-check-label" onclick="stretchedLinkClick(this)">@translator.Translate(userLanguage, "Fail")</label>
</div>
<button type="button" class="btn btn-outline-danger btn-sm" onclick="deleteInspectionRecordFieldOption(this)"><i class="bi bi-trash"></i></button>
</div>

View File

@ -0,0 +1,15 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<InspectionRecordTemplateFieldOption>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="d-grid p-2">
<button type="button" class="btn btn-outline-warning" onclick="addInspectionRecordFieldOption(this)">@translator.Translate(userLanguage, "Add Option")</button>
</div>
@foreach (InspectionRecordTemplateFieldOption fieldOption in Model)
{
@await Html.PartialAsync("Inspection/_InspectionRecordFieldOption", fieldOption)
}

View File

@ -0,0 +1,135 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model InspectionRecordInput
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "Add New Inspection Record"))</h5>
<button type="button" class="btn-close" onclick="hideAddInspectionRecordModal(true)" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="inspectionRecordDate">@translator.Translate(userLanguage, "Date")</label>
<div class="input-group">
<input type="text" id="inspectionRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Date inspection was performed")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="inspectionRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="inspectionRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage, "Odometer reading when inspected")" value="">
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('inspectionRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
</div>
<label for="inspectionRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="inspectionRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Description of Inspection performed")" value="@Model.Description">
<div class="row">
<div class="col-12">
<a onclick="showRecurringReminderSelector('inspectionRecordDescription', 'inspectionRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
</div>
</div>
<label for="inspectionRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="inspectionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the inspection")" value="">
@if (Model.Fields.Any())
{
<hr />
<div id="inspectionRecordFields">
@foreach (InspectionRecordTemplateField templateField in Model.Fields)
{
var templateFieldName = Guid.NewGuid().ToString();
<div data-type="field" data-fieldtype="@templateField.FieldType">
<label data-type="fieldDescription">@templateField.Description</label>
<div data-type="fieldOptions">
@if (templateField.FieldType == InspectionFieldType.Text)
{
<input type="text" class="form-control" data-type="fieldOption" placeholder="@templateField.Description">
}
else if (templateField.FieldType == InspectionFieldType.Check)
{
<div class="d-flex justify-content-between">
@foreach (InspectionRecordTemplateFieldOption fieldOption in templateField.Options)
{
<div class="form-check form-check-inline" data-type="fieldOptionContainer">
<input class="form-check-input" data-type="fieldOption" type="checkbox" role="switch" data-field="@(fieldOption.IsFail ? "fail" : "")">
<label class="form-check-label" data-type="fieldOptionText" onclick="stretchedLinkClick(this)">@fieldOption.Description</label>
</div>
}
</div>
}
else if (templateField.FieldType == InspectionFieldType.Radio)
{
<div class="d-flex justify-content-between">
@foreach (InspectionRecordTemplateFieldOption fieldOption in templateField.Options)
{
<div class="form-check form-check-inline" data-type="fieldOptionContainer">
<input class="form-check-input" data-type="fieldOption" type="radio" role="switch" name="@templateFieldName" data-field="@(fieldOption.IsFail ? "fail" : "")">
<label class="form-check-label" data-type="fieldOptionText" onclick="stretchedLinkClick(this)">@fieldOption.Description</label>
</div>
}
</div>
}
</div>
@if (templateField.HasNotes)
{
<div>
<label for="@($"{templateFieldName}Notes")">@translator.Translate(userLanguage, "Notes")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="@($"{templateFieldName}Notes")" data-type="fieldNotes" class="form-control" rows="5"></textarea>
</div>
}
@if (templateField.HasActionItem)
{
<div data-type="fieldActionItemContainer" style="display:none;">
<input type="text" data-type="fieldActionItemDescription" class="form-control" value="@templateField.ActionItemDescription">
<input type="text" data-type="fieldActionItemType" class="form-control" value="@templateField.ActionItemType">
<input type="text" data-type="fieldActionItemPriority" class="form-control" value="@templateField.ActionItemPriority">
</div>
}
<hr />
</div>
}
</div>
}
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
<label class="form-check-label" for="addReminderCheck">
@translator.Translate(userLanguage, "Add Reminder")
</label>
</div>
<label for="inspectionRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="inspectionRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideAddInspectionRecordModal(true)">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="saveinspectionRecordToVehicle()">@translator.Translate(userLanguage, "Add New Inspection Record")</button>
</div>
<script>
var recurringReminderRecordId = [];
function getInspectionRecordModelData() {
return { id: @Model.Id}
}
@foreach(int reminderRecordId in Model.ReminderRecordId)
{
@:recurringReminderRecordId.push(@reminderRecordId);
}
</script>

View File

@ -0,0 +1,80 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model InspectionRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew? translator.Translate(userLanguage, "Add New Inspection Record Template") : translator.Translate(userLanguage, "Edit Inspection Record Template"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditInspectionRecordTemplateModal({Model.Id}, true)")'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideInspectionRecordTemplateModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="inspectionRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="inspectionRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Description of Inspection")" value="@Model.Description">
<div class="row">
<div class="col-12">
<a onclick="showRecurringReminderSelector('inspectionRecordDescription', 'inspectionRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
</div>
</div>
<div id="inspectionRecordFields" style="overflow-y:auto;">
@foreach (InspectionRecordTemplateField inspectionRecordField in Model.Fields)
{
@await Html.PartialAsync("Inspection/_InspectionRecordField", inspectionRecordField)
}
</div>
<div class="d-grid p-2">
<button type="button" class="btn btn-warning" onclick="addInspectionRecordField()">@translator.Translate(userLanguage, "Add Field")</button>
</div>
<label for="inspectionRecordTemplateTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="inspectionRecordTemplateTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger me-auto" onclick="deleteInspectionRecordTemplate(@Model.Id)">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideInspectionRecordTemplateModal()">@translator.Translate(userLanguage, "Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveInspectionRecordTemplateToVehicle()">@translator.Translate(userLanguage, "Add")</button>
}
else
{
<div class="btn-group">
<button type="button" class="btn btn-primary btn-md mt-1 mb-1" onclick="saveInspectionRecordTemplateToVehicle(true)">@translator.Translate(userLanguage, "Edit")</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="duplicateInspectionRecordTemplateToVehicle()">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
</ul>
</div>
}
</div>
<script>
var recurringReminderRecordId = [];
function getInspectionRecordModelData() {
return { id: @Model.Id}
}
@foreach (int reminderRecordId in Model.ReminderRecordId)
{
@:recurringReminderRecordId.push(@reminderRecordId);
}
</script>

View File

@ -0,0 +1,63 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<InspectionRecordInput>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "Select Inspection Record Template"))</h5>
<button type="button" class="btn-close" onclick="hideInspectionRecordTemplateSelectorModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@if (Model.Any())
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-8">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Use")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Edit")</th>
</tr>
</thead>
<tbody>
@foreach (InspectionRecordInput inspectionRecordTemplate in Model)
{
<tr class="d-flex" id="supplyRows">
<td class="col-8 text-truncate">
@StaticHelper.TruncateStrings(inspectionRecordTemplate.Description)
@if (inspectionRecordTemplate.ReminderRecordId.Any())
{
<i class="bi bi-bell ms-2"></i>
}
@if (inspectionRecordTemplate.Fields.Any(x=>x.HasActionItem))
{
<i class="bi bi-tools ms-2"></i>
}
</td>
<td class="col-2"><button type="button" class="btn btn-primary" onclick="useInspectionRecordTemplate(@inspectionRecordTemplate.Id)"><i class="bi bi-plus-square"></i></button></td>
<td class="col-2"><button type="button" class="btn btn-warning" onclick="showEditInspectionRecordTemplateModal(@inspectionRecordTemplate.Id)"><i class="bi bi-pencil-square"></i></button></td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="row">
<div class="col-12">
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No templates are found.")</h4>
</div>
</div>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" onclick="showAddInspectionRecordTemplateModal()">@translator.Translate(userLanguage, "Add New Inspection Template")</button>
</div>

View File

@ -0,0 +1,101 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model InspectionRecord
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var hideZero = userConfig.HideZero;
var resultFields = Model.Results.Where(x => x.FieldType == InspectionFieldType.Check || x.FieldType == InspectionFieldType.Radio);
var textFields = Model.Results.Where(x => x.FieldType == InspectionFieldType.Text);
}
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "View Inspection Record"))</h5>
<button type="button" class="btn-close" onclick="hideAddInspectionRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<div class="d-flex"><label>@($"{translator.Translate(userLanguage, "Date")}: {Model.Date.ToShortDateString()}")</label></div>
<div class="d-flex"><label>@($"{translator.Translate(userLanguage, "Odometer")}: {Model.Mileage.ToString()}")</label></div>
<div class="d-flex"><label>@($"{translator.Translate(userLanguage, "Description")}: {Model.Description}")</label></div>
<div class="d-flex"><label>@($"{translator.Translate(userLanguage, "Result")}: {translator.Translate(userLanguage, Model.Failed ? "Fail" : "Pass")}")</label></div>
<div class="d-flex"><label>@($"{translator.Translate(userLanguage, "Cost")}: {(StaticHelper.HideZeroCost(Model.Cost, hideZero))}")</label></div>
@foreach(InspectionRecordResult textResult in textFields)
{
<div class="d-flex"><label>@($"{textResult.Description}: {textResult.Values?.FirstOrDefault(x => x.IsSelected)?.Description}")</label></div>
}
@if (resultFields.Any())
{
<hr />
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-9 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Result")</th>
</tr>
</thead>
<tbody>
@foreach (InspectionRecordResult fieldResult in resultFields)
{
<tr class="d-flex user-select-none">
<td class="col-9 flex-grow-1 flex-shrink-1 text-truncate">
@fieldResult.Description
<div class="d-flex flex-wrap">
@foreach (InspectionRecordResultValue fieldValue in fieldResult.Values)
{
if (fieldResult.FieldType == InspectionFieldType.Radio)
{
<span class="inspection-value me-2 @(fieldValue.IsFail && fieldValue.IsSelected ? "inspection-value-fail text-danger" : "") @(fieldValue.IsSelected ? "inspection-value-selected" : "")">@fieldValue.Description</span>
}
else if (fieldResult.FieldType == InspectionFieldType.Check)
{
<span class="inspection-value me-2 @(fieldValue.IsFail && !fieldValue.IsSelected ? "text-danger" : "")"><i class="bi @(fieldValue.IsSelected ? "bi-check-lg" : "bi-x-lg") me-2"></i>@fieldValue.Description</span>
}
}
</div>
</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate">
@(fieldResult.Failed? translator.Translate(userLanguage, "Fail") : translator.Translate(userLanguage, "Pass"))
</td>
</tr>
@if (!string.IsNullOrWhiteSpace(fieldResult.Notes))
{
<tr class="d-flex user-select-none">
<td class="col-12 flex-grow-1 flex-shrink-1 flex-wrap">
<div class="stickerNote ms-1 me-1 p-1">
@(fieldResult.Notes)
</div>
</td>
</tr>
}
}
</tbody>
</table>
}
<label for="inspectionRecordTag">@translator.Translate(userLanguage, "Tags")</label>
<select multiple class="form-select" id="inspectionRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger me-auto" onclick="deleteInspectionRecord(@Model.Id)">@translator.Translate(userLanguage, "Delete")</button>
<button type="button" class="btn btn-secondary" onclick="hideAddInspectionRecordModal()">@translator.Translate(userLanguage, "Close")</button>
<button type="button" class="btn btn-primary" onclick="updateInspectionRecordTag(@Model.Id)">@translator.Translate(userLanguage, "Edit")</button>
</div>
<script>
setMarkDownStickerNotes();
</script>

View File

@ -0,0 +1,168 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<InspectionRecord>
@{
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.InspectionRecord);
}
<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" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Inspection Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('inspection-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showInspectionRecordTemplateSelectorModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Inspection 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="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('inspection-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('InspectionRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'InspectionRecord')" type="checkbox" id="chkCol_Date" checked>
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('InspectionRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'InspectionRecord')" type="checkbox" id="chkCol_Odometer" checked>
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('InspectionRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'InspectionRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('InspectionRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'InspectionRecord')" type="checkbox" id="chkCol_Cost" checked>
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('InspectionRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='result' onChange="showTableColumns(this, 'InspectionRecord')" type="checkbox" id="chkCol_Result" checked>
<label class="form-check-label stretched-link" for="chkCol_Result">@translator.Translate(userLanguage, "Result")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('InspectionRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'InspectionRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
</ul>
</div>
}
else
{
<button onclick="showInspectionRecordTemplateSelectorModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Inspection 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="@config.GetLogoUrl()" class="lubelogger-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('inspection-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="result">@translator.Translate(userLanguage, "Result")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
</tr>
</thead>
<tbody>
@foreach (InspectionRecord inspectionRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@inspectionRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditInspectionRecordModal,@inspectionRecord.Id)" data-tags='@string.Join(" ", inspectionRecord.Tags)'>
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(inspectionRecord.Date)">@inspectionRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(inspectionRecord.Mileage == default ? "---" : inspectionRecord.Mileage.ToString())</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@inspectionRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(inspectionRecord.Cost, hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="result">@(inspectionRecord.Failed ? translator.Translate(userLanguage, "Fail") : translator.Translate(userLanguage, "Pass"))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", inspectionRecord.Files)</td>
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint lubelogger-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="inspectionRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog" role="document">
<div class="modal-content" id="inspectionRecordModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="inspectionRecordTemplateModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog" role="document">
<div class="modal-content" id="inspectionRecordTemplateModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="inspectionRecordTemplateEditModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog" role="document">
<div class="modal-content" id="inspectionRecordTemplateEditModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'InspectionRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'InspectionRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@ -86,7 +86,7 @@
</div>
</div>
</div>
@if(years.Count() > 5)
@if(years.Where(x => x != default).Count() > 5)
{
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-costdata-prev"><i class="bi bi-caret-left-fill"></i></button>

View File

@ -146,7 +146,7 @@
@if (Model.CustomWidgetsConfigured)
{
<div class="d-grid">
<button onclick="loadCustomWidgets()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Additional Widgets")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
<button onclick="showCustomWidgets()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Additional Widgets")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
}
</div>
@ -167,6 +167,7 @@
</div>
</div>
</div>
<script>loadCustomWidgets()</script>
}
<script>

View File

@ -274,4 +274,116 @@
</div>
}
<script>setMarkDownStickerNotes()</script>
}
} else if (Model.InspectionRecords.Any())
{
@foreach (InspectionRecord inspectionRecord in Model.InspectionRecords)
{
var resultFields = inspectionRecord.Results.Where(x => x.FieldType == InspectionFieldType.Check || x.FieldType == InspectionFieldType.Radio);
var textFields = inspectionRecord.Results.Where(x => x.FieldType == InspectionFieldType.Text);
<div style="page-break-after: always;">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
</div>
<hr />
<div class="row">
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
<li class="list-group-item">
<span class="lead">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</span>
</li>
@foreach (ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
</ul>
</div>
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {inspectionRecord.Description}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Result")}: {translator.Translate(userLanguage, inspectionRecord.Failed ? "Fail" : "Pass")}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {inspectionRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Odometer")}: {inspectionRecord.Mileage}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {inspectionRecord.Cost.ToString("C")}")
</li>
@foreach (InspectionRecordResult textResult in textFields)
{
<li class="list-group-item">
@($"{textResult.Description}: {textResult.Values?.FirstOrDefault(x => x.IsSelected)?.Description}")
</li>
}
</ul>
</div>
</div>
<hr />
<div class="row">
<div class="col-12">
@if (resultFields.Any())
{
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-9 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Result")</th>
</tr>
</thead>
<tbody>
@foreach (InspectionRecordResult fieldResult in resultFields)
{
<tr class="d-flex user-select-none">
<td class="col-9 flex-grow-1 flex-shrink-1 text-truncate">
@fieldResult.Description
<div class="d-flex flex-wrap">
@foreach (InspectionRecordResultValue fieldValue in fieldResult.Values)
{
if (fieldResult.FieldType == InspectionFieldType.Radio)
{
<span class="inspection-value me-2 @(fieldValue.IsFail && fieldValue.IsSelected ? "inspection-value-fail text-danger" : "") @(fieldValue.IsSelected ? "inspection-value-selected" : "")">@fieldValue.Description</span>
}
else if (fieldResult.FieldType == InspectionFieldType.Check)
{
<span class="inspection-value me-2 @(fieldValue.IsFail && !fieldValue.IsSelected ? "text-danger" : "")"><i class="bi @(fieldValue.IsSelected ? "bi-check-lg" : "bi-x-lg") me-2"></i>@fieldValue.Description</span>
}
}
</div>
</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate">
@(fieldResult.Failed? translator.Translate(userLanguage, "Fail") : translator.Translate(userLanguage, "Pass"))
</td>
</tr>
@if (!string.IsNullOrWhiteSpace(fieldResult.Notes))
{
<tr class="d-flex user-select-none">
<td class="col-12 flex-grow-1 flex-shrink-1 flex-wrap">
<div class="stickerNote ms-1 me-1 p-1">
@(fieldResult.Notes)
</div>
</td>
</tr>
}
}
</tbody>
</table>
}
</div>
</div>
</div>
}
<script>setMarkDownStickerNotes()</script>
}

View File

@ -33,7 +33,7 @@
"PreferredGasUnit": "",
"UserLanguage": "en_US",
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
"TabOrder": [ 8, 9, 10, 0, 1, 4, 2, 7, 3, 6, 5 ],
"TabOrder": [ 8, 9, 10, 0, 1, 4, 2, 7, 3, 6, 12, 5 ],
"DefaultTab": 8,
"UserNameHash": "",
"UserPasswordHash": "",

View File

@ -647,4 +647,17 @@ html[data-bs-theme="light"] .frosted {
}
.garage-item.garage-active .card {
border-color: #6ea8fe;
}
}
.inspection-value {
padding: 0.35em 0.65em;
text-align: center;
vertical-align: baseline;
white-space: nowrap;
border-radius: 0.375rem;
}
.inspection-value.inspection-value-selected{
border: 1px solid;
}
.inspection-value.inspection-value-selected.inspection-value-fail {
border-color: #dc3545;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,452 @@
function showInspectionRecordTemplateSelectorModal() {
var vehicleId = GetVehicleId().vehicleId;
$.get(`/Vehicle/GetInspectionRecordTemplatesByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {
$("#inspectionRecordTemplateModalContent").html(data);
$('#inspectionRecordTemplateModal').modal('show');
clearModalContentOnHide($('#inspectionRecordTemplateModal'));
}
});
}
function hideInspectionRecordTemplateSelectorModal() {
$('#inspectionRecordTemplateModal').modal('hide');
}
function showAddInspectionRecordTemplateModal() {
$.get('/Vehicle/GetAddInspectionRecordTemplatePartialView', function (data) {
if (data) {
$("#inspectionRecordTemplateEditModalContent").html(data);
//initiate tag selector
initTagSelector($("#inspectionRecordTemplateTag"));
hideInspectionRecordTemplateSelectorModal();
$('#inspectionRecordTemplateEditModal').modal('show');
clearModalContentOnHide($('#inspectionRecordTemplateEditModal'));
}
});
}
function showEditInspectionRecordTemplateModal(inspectionRecordTemplateId) {
$.get(`/Vehicle/GetEditInspectionRecordTemplatePartialView?inspectionRecordTemplateId=${inspectionRecordTemplateId}`, function (data) {
if (data) {
$("#inspectionRecordTemplateEditModalContent").html(data);
//initiate tag selector
initTagSelector($("#inspectionRecordTemplateTag"));
hideInspectionRecordTemplateSelectorModal();
$('#inspectionRecordTemplateEditModal').modal('show');
clearModalContentOnHide($('#inspectionRecordTemplateEditModal'));
}
});
}
function hideInspectionRecordTemplateModal() {
$('#inspectionRecordTemplateEditModal').modal('hide');
showInspectionRecordTemplateSelectorModal();
}
function addInspectionRecordField() {
$.get('/Vehicle/GetAddInspectionRecordFieldPartialView', function (data) {
$("#inspectionRecordFields").append(data);
});
}
function deleteInspectionRecordField(e) {
$(e).closest('[data-type="field"]').remove();
}
function duplicateInspectionRecordField(e) {
let currentField = $(e).closest('[data-type="field"]');
let clonedField = currentField.clone();
//$("#inspectionRecordFields").append(clonedField);
clonedField.insertAfter(currentField);
}
function setDropDownOptionSelected(dropDownElem) {
let selectedVal = $(dropDownElem).val();
$(dropDownElem).find('option').removeAttr('selected');
$(dropDownElem).find(`option[value="${selectedVal}"]`).attr('selected', '');
}
function handleInspectionRecordFieldTypeChange(e) {
setDropDownOptionSelected(e);
let selectedVal = $(e).val();
switch (selectedVal) {
case 'Radio':
case 'Check':
$.get('/Vehicle/GetAddInspectionRecordFieldOptionsPartialView', function (data) {
$(e).closest('[data-type="field"]').find('[data-type="fieldOptions"]').html(data);
});
$(e).closest('[data-type="field"]').find('[data-type="fieldActionItem"]').show();
break;
case 'Text':
$(e).closest('[data-type="field"]').find('[data-type="fieldOptions"]').html("");
$(e).closest('[data-type="field"]').find('[data-type="fieldActionItem"]').hide();
break;
}
}
function handleInspectionRecordFieldHasActionItemChange(e) {
if ($(e).is(":checked")) {
$(e).closest('[data-type="field"]').find('[data-type="fieldActionItemContainer"]').collapse('show');
} else {
$(e).closest('[data-type="field"]').find('[data-type="fieldActionItemContainer"]').collapse('hide');
}
}
function addInspectionRecordFieldOption(e) {
$.get('/Vehicle/GetAddInspectionRecordFieldOptionPartialView', function (data) {
$(e).closest('[data-type="field"]').find('[data-type="fieldOptions"]').append(data);
});
}
function deleteInspectionRecordFieldOption(e) {
$(e).closest('[data-type="fieldOption"]').remove();
}
function getAndValidateInspectionRecordTemplate() {
let hasError = false;
let inspectionDescription = $("#inspectionRecordDescription").val();
if (inspectionDescription.trim() == '') {
hasError = true;
$("#inspectionRecordDescription").addClass("is-invalid");
} else {
$("#inspectionRecordDescription").removeClass("is-invalid");
}
let inspectionTags = $("#inspectionRecordTemplateTag").val();
let inspectionRecordId = getInspectionRecordModelData().id;
let vehicleId = GetVehicleId().vehicleId;
let inspectionRecordTemplateData = {
id: inspectionRecordId,
vehicleId: vehicleId,
description: inspectionDescription,
tags: inspectionTags,
reminderRecordId: recurringReminderRecordId
}
let templateFields = [];
//process fields
$('#inspectionRecordFields > [data-type="field"]').map((index, elem) => {
let fieldElem = $(elem);
let hasActionItem = fieldElem.find('[data-type="fieldHasActionItem"]').is(":checked");
let fieldType = fieldElem.find('[data-type="fieldType"]').val();
let fieldDescriptionElem = fieldElem.find('[data-type="fieldDescription"]');
if (fieldDescriptionElem.val().trim() == '') {
hasError = true;
fieldDescriptionElem.addClass('is-invalid');
} else {
fieldDescriptionElem.removeClass('is-invalid');
}
let fieldData = {
description: fieldDescriptionElem.val(),
fieldType: fieldType,
hasNotes: fieldElem.find('[data-type="fieldHasNotes"]').is(":checked"),
hasActionItem: hasActionItem
};
if (hasActionItem) {
fieldData["actionItemType"] = fieldElem.find('[data-type="fieldActionItemType"]').val();
fieldData["actionItemPriority"] = fieldElem.find('[data-type="fieldActionItemPriority"]').val();
let actionItemDescriptionElem = fieldElem.find('[data-type="fieldActionItemDescription"]');
fieldData["actionItemDescription"] = actionItemDescriptionElem.val();
if (actionItemDescriptionElem.val().trim() == '') {
hasError = true;
actionItemDescriptionElem.addClass('is-invalid');
} else {
actionItemDescriptionElem.removeClass('is-invalid');
}
}
if (fieldType != 'Text') {
let fieldOptions = [];
fieldElem.find('[data-type="fieldOptions"]').find('[data-type="fieldOption"]').map((optionIndex, optionElem) => {
let fieldOptionElem = $(optionElem);
let fieldOptionTextElem = fieldOptionElem.find('[data-type="fieldOptionText"]');
if (fieldOptionTextElem.val().trim() == '') {
hasError = true;
fieldOptionTextElem.addClass('is-invalid');
} else {
fieldOptionTextElem.removeClass('is-invalid');
}
fieldOptions.push({
description: fieldOptionTextElem.val(),
isFail: fieldOptionElem.find('[data-type="fieldOptionIsFail"]').is(":checked")
});
});
fieldData["options"] = fieldOptions;
if (fieldOptions.length == 0) {
//user has not supplied any options
fieldElem.find('[data-type="fieldType"]').addClass('is-invalid');
hasError = true;
} else {
fieldElem.find('[data-type="fieldType"]').removeClass('is-invalid');
}
}
else {
fieldElem.find('[data-type="fieldType"]').removeClass('is-invalid');
}
templateFields.push(fieldData);
});
inspectionRecordTemplateData["fields"] = templateFields;
inspectionRecordTemplateData["hasError"] = hasError;
return inspectionRecordTemplateData;
}
function saveInspectionRecordTemplateToVehicle(isEdit) {
let formValues = getAndValidateInspectionRecordTemplate();
if (formValues.hasError) {
errorToast("Please check the form data");
return;
}
$.post('/Vehicle/SaveInspectionRecordTemplateToVehicleId', { inspectionRecordTemplate: formValues }, function (data) {
if (data) {
successToast(isEdit ? "Inspection Record Template Updated" : "Inspection Record Template Added.");
hideInspectionRecordTemplateModal();
getVehicleCollisionRecords(formValues.vehicleId);
} else {
errorToast(genericErrorMessage());
}
})
}
function deleteInspectionRecordTemplate(inspectionRecordTemplateId) {
$("#workAroundInput").show();
Swal.fire({
title: "Confirm Deletion?",
text: "Deleted Inspection Templates cannot be restored.",
showCancelButton: true,
confirmButtonText: "Delete",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post(`/Vehicle/DeleteInspectionRecordTemplateById?inspectionRecordTemplateId=${inspectionRecordTemplateId}`, function (data) {
$("#workAroundInput").hide();
if (data) {
successToast("Inspection Template Deleted");
hideInspectionRecordTemplateModal();
} else {
errorToast(genericErrorMessage());
}
});
} else {
$("#workAroundInput").hide();
}
});
}
function useInspectionRecordTemplate(inspectionRecordTemplateId) {
$.get(`/Vehicle/GetAddInspectionRecordPartialView?inspectionRecordTemplateId=${inspectionRecordTemplateId}`, function (data) {
if (data) {
$("#inspectionRecordModalContent").html(data);
hideInspectionRecordTemplateSelectorModal();
//initiate datepicker
initDatePicker($('#inspectionRecordDate'));
initTagSelector($("#inspectionRecordTag"));
$("#inspectionRecordModal").modal('show');
clearModalContentOnHide($("#inspectionRecordModal"));
} else {
errorToast(genericErrorMessage());
}
});
}
function hideAddInspectionRecordModal(showSelector) {
$("#inspectionRecordModalContent").html('');
$("#inspectionRecordModal").modal('hide');
if (showSelector) {
showInspectionRecordTemplateSelectorModal();
}
}
function getAndValidateInspectionRecord() {
let hasError = false;
let inspectionDescription = $("#inspectionRecordDescription").val();
let inspectionDate = $("#inspectionRecordDate").val();
let inspectionMileage = $("#inspectionRecordMileage").val();
let inspectionCost = $("#inspectionRecordCost").val();
let inspectionTags = $("#inspectionRecordTag").val();
let inspectionRecordId = 0;
var addReminderRecord = $("#addReminderCheck").is(":checked");
let vehicleId = GetVehicleId().vehicleId;
//Odometer Adjustments
if (isNaN(inspectionMileage) && GetVehicleId().odometerOptional) {
inspectionMileage = '0';
}
inspectionMileage = GetAdjustedOdometer(inspectionRecordId, inspectionMileage);
//validations
if (inspectionDescription.trim() == '') {
hasError = true;
$("#inspectionRecordDescription").addClass("is-invalid");
} else {
$("#inspectionRecordDescription").removeClass("is-invalid");
}
if (inspectionDate.trim() == '') { //eliminates whitespace.
hasError = true;
$("#inspectionRecordDate").addClass("is-invalid");
} else {
$("#inspectionRecordDate").removeClass("is-invalid");
}
if (inspectionMileage.trim() == '' || isNaN(inspectionMileage) || parseInt(inspectionMileage) < 0) {
hasError = true;
$("#inspectionRecordMileage").addClass("is-invalid");
} else {
$("#inspectionRecordMileage").removeClass("is-invalid");
}
if (inspectionCost.trim() == '' || !isValidMoney(inspectionCost)) {
hasError = true;
$("#inspectionRecordCost").addClass("is-invalid");
} else {
$("#inspectionRecordCost").removeClass("is-invalid");
}
let inspectionRecordData = {
id: inspectionRecordId,
vehicleId: vehicleId,
date: inspectionDate,
mileage: inspectionMileage,
cost: inspectionCost,
description: inspectionDescription,
tags: inspectionTags,
reminderRecordId: recurringReminderRecordId,
files: uploadedFiles,
addReminderRecord: addReminderRecord
}
let recordFields = [];
//process fields
$('#inspectionRecordFields > [data-type="field"]').map((index, elem) => {
let fieldElem = $(elem);
let hasActionItem = fieldElem.find('[data-type="fieldActionItemContainer"]').length > 0;
let hasNotes = fieldElem.find('[data-type="fieldNotes"]').length > 0;
let fieldType = fieldElem.attr('data-fieldtype');
let fieldData = {
description: fieldElem.find('[data-type="fieldDescription"]').text(),
fieldType: fieldType,
hasNotes: hasNotes,
hasActionItem: hasActionItem
};
if (hasActionItem) {
fieldData["actionItemDescription"] = fieldElem.find('[data-type="fieldActionItemDescription"]').val();
fieldData["actionItemType"] = fieldElem.find('[data-type="fieldActionItemType"]').val();
fieldData["actionItemPriority"] = fieldElem.find('[data-type="fieldActionItemPriority"]').val();
}
if (hasNotes) {
let fieldNoteElem = fieldElem.find('[data-type="fieldNotes"]');
fieldData["notes"] = fieldNoteElem.val();
if (fieldNoteElem.val().trim() == '') {
hasError = true;
fieldNoteElem.addClass('is-invalid');
} else {
fieldNoteElem.removeClass('is-invalid');
}
}
if (fieldType != 'Text') {
let fieldOptions = [];
fieldElem.find('[data-type="fieldOptions"]').find('[data-type="fieldOption"]').map((optionIndex, optionElem) => {
let fieldOptionElem = $(optionElem);
fieldOptions.push({
description: fieldOptionElem.closest('[data-type="fieldOptionContainer"]').find('[data-type="fieldOptionText"]').text(),
isSelected: fieldOptionElem.is(":checked"),
isFail: fieldOptionElem.attr('data-field') == 'fail'
});
});
fieldData["options"] = fieldOptions;
//user must select at least one option for radio fields
if (fieldType == 'Radio' && fieldOptions.filter(x=>x.isSelected).length == 0) {
fieldElem.find('[data-type="fieldOptions"]').find('[data-type="fieldOption"]').addClass('is-invalid');
hasError = true;
} else {
fieldElem.find('[data-type="fieldOptions"]').find('[data-type="fieldOption"]').removeClass('is-invalid');
}
} else {
//handle text field
let fieldOptions = [];
let fieldTextOptionElem = fieldElem.find('[data-type="fieldOptions"]').find('[data-type="fieldOption"]');
if (fieldTextOptionElem.val().trim() == '') {
hasError = true;
fieldTextOptionElem.addClass('is-invalid');
} else {
fieldTextOptionElem.removeClass('is-invalid');
}
fieldOptions.push({
description: fieldTextOptionElem.val(),
isSelected: true,
isFail: false
});
fieldData["options"] = fieldOptions;
}
recordFields.push(fieldData);
});
inspectionRecordData["fields"] = recordFields;
inspectionRecordData["hasError"] = hasError;
return inspectionRecordData;
}
function saveinspectionRecordToVehicle() {
//get values
var formValues = getAndValidateInspectionRecord();
//validate
if (formValues.hasError) {
errorToast("Please check the form data");
return;
}
$.post('/Vehicle/SaveInspectionRecordToVehicleId', { inspectionRecord: formValues }, function (data) {
if (data) {
successToast("Inspection Record Added.");
hideAddInspectionRecordModal();
saveScrollPosition();
getVehicleInspectionRecords(formValues.vehicleId);
if (formValues.addReminderRecord) {
setTimeout(function () { showAddReminderModal(formValues); }, 500);
}
} else {
errorToast(genericErrorMessage());
}
})
}
function updateInspectionRecordTag(recordId) {
let inspectionTags = $("#inspectionRecordTag").val();
let vehicleId = GetVehicleId().vehicleId;
$.post('/Vehicle/UpdateInspectionRecordTags', { inspectionRecordId: recordId, tags: inspectionTags }, function (data) {
if (data) {
successToast("Inspection Record Updated.");
hideAddInspectionRecordModal();
saveScrollPosition();
getVehicleInspectionRecords(vehicleId);
} else {
errorToast(genericErrorMessage());
}
})
}
function showEditInspectionRecordModal(inspectionRecordId) {
$.get(`/Vehicle/GetViewInspectionRecordPartialView?inspectionRecordId=${inspectionRecordId}`, function (data) {
if (data) {
$("#inspectionRecordModalContent").html(data);
//initiate tag selector
initTagSelector($("#inspectionRecordTag"));
$("#inspectionRecordModal").modal('show');
clearModalContentOnHide($("#inspectionRecordModal"));
} else {
errorToast(genericErrorMessage());
}
});
}
function deleteInspectionRecord(inspectionRecordId) {
$("#workAroundInput").show();
Swal.fire({
title: "Confirm Deletion?",
text: "Deleted Inspection Records cannot be restored.",
showCancelButton: true,
confirmButtonText: "Delete",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post(`/Vehicle/DeleteInspectionRecordById?inspectionRecordId=${inspectionRecordId}`, function (data) {
if (data) {
hideAddInspectionRecordModal();
successToast("Inspection Record Deleted");
var vehicleId = GetVehicleId().vehicleId;
getVehicleInspectionRecords(vehicleId);
} else {
errorToast(genericErrorMessage());
}
});
} else {
$("#workAroundInput").hide();
}
});
}
function moveInspectionRecordField(e, isDown) {
let currentField = $(e).closest('[data-type="field"]');
if (isDown) {
let nextField = currentField.next('[data-type="field"]');
if (nextField.length != 0) {
currentField.insertAfter(nextField);
}
} else {
let prevField = currentField.prev('[data-type="field"]');
if (prevField.length != 0) {
currentField.insertBefore(prevField);
}
}
}
function duplicateInspectionRecordTemplateToVehicle() {
let inspectionRecordsIds = [];
inspectionRecordsIds.push(getInspectionRecordModelData().id);
duplicateRecordsToOtherVehicles(inspectionRecordsIds, 'InspectionRecord');
}

View File

@ -412,10 +412,17 @@ function loadMapSearchResult(id, recordType) {
hideDataTable();
loadGlobalSearchResult(id, recordType);
}
function loadRecordAttachment(id, recordType) {
closeAttachmentPreview();
$(".modal.show").modal('hide');
loadGlobalSearchResult(id, recordType);
}
function showCustomWidgets() {
$("#vehicleCustomWidgetsModal").modal('show');
}
function loadCustomWidgets() {
$.get('/Vehicle/GetAdditionalWidgets', function (data) {
$("#vehicleCustomWidgetsModalContent").html(data);
$("#vehicleCustomWidgetsModal").modal('show');
})
}
function hideCustomWidgetsModal() {

View File

@ -915,6 +915,9 @@ function deleteRecords(ids, source) {
friendlySource = "Fuel Records";
refreshDataCallBack = getVehicleGasRecords;
break;
case "InspectionRecord":
friendlySource = "Inspection Records";
refreshDataCallBack = getVehicleInspectionRecords;
}
Swal.fire({
@ -1061,6 +1064,9 @@ function duplicateRecordsToOtherVehicles(ids, source) {
friendlySource = "Plan";
refreshDataCallBack = getVehiclePlanRecords;
break;
case "InspectionRecord":
friendlySource = "Inspection Records";
refreshDataCallBack = getVehicleInspectionRecords;
}
$.get(`/Home/GetVehicleSelector?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
@ -1783,4 +1789,17 @@ function getTabNameFromURL(defaultValue) {
} else {
return `${currentTab}-tab`;
}
}
function stretchedLinkClick(e) {
let closestCheckElem = $(e).closest('.form-check').find('.form-check-input');
if (closestCheckElem.prop('checked')) {
closestCheckElem.prop('checked', false).trigger('change');
} else {
closestCheckElem.prop('checked', true).trigger('change');
}
}
function clearModalContentOnHide(modalElem) {
modalElem.off('hidden.bs.modal').on('hidden.bs.modal', () => {
modalElem.find('.modal-content').html('');
});
}

View File

@ -36,6 +36,9 @@
case "odometer-tab":
getVehicleOdometerRecords(vehicleId);
break;
case "inspection-tab":
getVehicleInspectionRecords(vehicleId);
break;
}
$(`.lubelogger-tab #${e.target.id}`).addClass('active');
$(`.lubelogger-mobile-nav #${e.target.id}`).addClass('active');
@ -74,6 +77,9 @@
case "odometer-tab":
$("#odometer-tab-pane").html("");
break;
case "inspection-tab":
$("#inspection-tab-pane").html("");
break;
}
$(`.lubelogger-tab #${e.relatedTarget.id}`).removeClass('active');
$(`.lubelogger-mobile-nav #${e.relatedTarget.id}`).removeClass('active');
@ -159,6 +165,7 @@ function getVehicleTaxRecords(vehicleId) {
if (data) {
$("#tax-tab-pane").html(data);
restoreScrollPosition();
getVehicleHaveImportantReminders(vehicleId);
}
});
}
@ -171,6 +178,15 @@ function getVehicleReminders(vehicleId) {
}
});
}
function getVehicleInspectionRecords(vehicleId) {
$.get(`/Vehicle/GetInspectionRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {
$("#inspection-tab-pane").html(data);
restoreScrollPosition();
getVehicleHaveImportantReminders(vehicleId);
}
});
}
function getVehicleReport(vehicleId) {
$.get(`/Vehicle/GetReportPartialView?vehicleId=${vehicleId}`, function (data) {
if (data) {
@ -301,6 +317,21 @@ function moveRecord(recordId, source, dest) {
}
});
}
function loadSelectedRecurringReminder() {
if (recurringReminderRecordId != undefined && recurringReminderRecordId.length > 0) {
if (recurringReminderRecordId.length > 1) {
//multiple reminders
$('#multipleRemindersCheck').prop('checked', true);
$('#multipleRemindersCheck').trigger('change');
recurringReminderRecordId.map(x => {
$(`#recurringReminder_${x}`).prop('checked', true);
});
}
else if (recurringReminderRecordId.length == 1) {
$("#recurringReminderInput").val(recurringReminderRecordId[0]);
}
}
}
function showRecurringReminderSelector(descriptionFieldName, noteFieldName) {
$.get(`/Vehicle/GetRecurringReminderRecordsByVehicleId?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
if (data) {
@ -310,6 +341,9 @@ function showRecurringReminderSelector(descriptionFieldName, noteFieldName) {
html: data,
confirmButtonText: 'Select',
focusConfirm: false,
didRender: () => {
loadSelectedRecurringReminder();
},
preConfirm: () => {
//validate
var selectedRecurringReminderData = getAndValidateSelectedRecurringReminder();
@ -671,48 +705,101 @@ function handleGlobalSearchKeyPress(event) {
function loadGlobalSearchResult(recordId, recordType) {
hideGlobalSearch();
switch (recordType) {
case "ServiceRecord":
$('#servicerecord-tab').tab('show');
waitForElement('#serviceRecordModalContent', showEditServiceRecordModal, recordId);
break;
case "RepairRecord":
$('#accident-tab').tab('show');
waitForElement('#collisionRecordModalContent', showEditCollisionRecordModal, recordId);
break;
case "UpgradeRecord":
$('#upgrade-tab').tab('show');
waitForElement('#upgradeRecordModalContent', showEditUpgradeRecordModal, recordId);
break;
case "TaxRecord":
$('#tax-tab').tab('show');
waitForElement('#taxRecordModalContent', showEditTaxRecordModal, recordId);
break;
case "SupplyRecord":
$('#supply-tab').tab('show');
waitForElement('#supplyRecordModalContent', showEditSupplyRecordModal, recordId);
break;
case "NoteRecord":
$('#notes-tab').tab('show');
waitForElement('#noteModalContent', showEditNoteModal, recordId);
break;
case "OdometerRecord":
$('#odometer-tab').tab('show');
waitForElement('#odometerRecordModalContent', showEditOdometerRecordModal, recordId);
break;
case "ReminderRecord":
$('#reminder-tab').tab('show');
waitForElement('#reminderRecordModalContent', showEditReminderRecordModal, recordId);
break;
case "GasRecord":
$('#gas-tab').tab('show');
waitForElement('#gasRecordModalContent', showEditGasRecordModal, recordId);
break;
case "PlanRecord":
$('#plan-tab').tab('show');
waitForElement('#planRecordModalContent', showEditPlanRecordModal, recordId);
break;
}
$.post(`/Vehicle/CheckRecordExist?vehicleId=${GetVehicleId().vehicleId}&importMode=${recordType}&recordId=${recordId}`, function (data) {
if (data.success) {
switch (recordType) {
case "ServiceRecord":
if ($('#servicerecord-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#servicerecord-tab').tab('show');
waitForElement('#serviceRecordModalContent', showEditServiceRecordModal, recordId);
break;
case "RepairRecord":
if ($('#accident-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#accident-tab').tab('show');
waitForElement('#collisionRecordModalContent', showEditCollisionRecordModal, recordId);
break;
case "UpgradeRecord":
if ($('#upgrade-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#upgrade-tab').tab('show');
waitForElement('#upgradeRecordModalContent', showEditUpgradeRecordModal, recordId);
break;
case "TaxRecord":
if ($('#tax-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#tax-tab').tab('show');
waitForElement('#taxRecordModalContent', showEditTaxRecordModal, recordId);
break;
case "SupplyRecord":
if ($('#supply-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#supply-tab').tab('show');
waitForElement('#supplyRecordModalContent', showEditSupplyRecordModal, recordId);
break;
case "NoteRecord":
if ($('#notes-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#notes-tab').tab('show');
waitForElement('#noteModalContent', showEditNoteModal, recordId);
break;
case "OdometerRecord":
if ($('#odometer-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#odometer-tab').tab('show');
waitForElement('#odometerRecordModalContent', showEditOdometerRecordModal, recordId);
break;
case "ReminderRecord":
if ($('#reminder-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#reminder-tab').tab('show');
waitForElement('#reminderRecordModalContent', showEditReminderRecordModal, recordId);
break;
case "GasRecord":
if ($('#gas-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#gas-tab').tab('show');
waitForElement('#gasRecordModalContent', showEditGasRecordModal, recordId);
break;
case "PlanRecord":
if ($('#plan-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#plan-tab').tab('show');
waitForElement('#planRecordModalContent', showEditPlanRecordModal, recordId);
break;
case "InspectionRecord":
if ($('#inspection-tab').hasClass('d-none')) {
errorToast(`${recordType} Tab Not Enabled`);
return;
}
$('#inspection-tab').tab('show');
waitForElement("#inspectionRecordModalContent", showEditInspectionRecordModal, recordId);
}
} else {
errorToast(data.message);
}
})
}
function loadDefaultTab() {
//check if tab param exists
@ -756,5 +843,8 @@ function getDefaultTabName() {
case "OdometerRecord":
return 'odometer';
break;
case "InspectionRecord":
return 'inspection';
break;
}
}