Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfd83b5b4e | ||
|
|
e468261095 | ||
|
|
1362dc87df | ||
|
|
b89902bbdb | ||
|
|
f31b70a6dc | ||
|
|
47a1f0c4d5 | ||
|
|
3234b530d5 | ||
|
|
75d16544db | ||
|
|
7179b60116 | ||
|
|
7cb5d8254f | ||
|
|
6ab7ee6e4f | ||
|
|
a57ce55f8b | ||
|
|
190484762d | ||
|
|
78554ade5d | ||
|
|
364a13226a | ||
|
|
691813838c | ||
|
|
0defb0fadf | ||
|
|
08bf00ee37 | ||
|
|
522322ee2a | ||
|
|
062e3600e7 | ||
|
|
a92160f6d7 | ||
|
|
4adf967f7f | ||
|
|
fd6bd98a25 | ||
|
|
fe76f32778 | ||
|
|
897b4f2efe | ||
|
|
267f903ffe | ||
|
|
db884d0a6a | ||
|
|
d1190e7ddb | ||
|
|
dae8ab679e | ||
|
|
86ec9409c3 | ||
|
|
2c4ddb0c38 | ||
|
|
44d10f11ca | ||
|
|
6cf733b9c6 | ||
|
|
1eb6e2cedf | ||
|
|
ef4deaba8f | ||
|
|
e8c196c2fa | ||
|
|
f7c9db6353 | ||
|
|
4ce720ff97 | ||
|
|
a96011629b | ||
|
|
9dcdcf97e8 | ||
|
|
5148338f52 | ||
|
|
0d3c04d8f8 | ||
|
|
15328a14b4 | ||
|
|
2d092f722a | ||
|
|
8825cb9b9b | ||
|
|
2f17e303ab | ||
|
|
4b56c8a343 | ||
|
|
7b6b62c623 | ||
|
|
07f5e66491 | ||
|
|
fe633f3220 | ||
|
|
af1090553f | ||
|
|
92c2e66660 | ||
|
|
08372f9dcb | ||
|
|
61c2600286 | ||
|
|
163a33ae3a | ||
|
|
37d064aa62 |
1
.env
1
.env
@@ -2,7 +2,6 @@ LC_ALL=en_US.UTF-8
|
||||
LANG=en_US.UTF-8
|
||||
MailConfig__EmailServer=""
|
||||
MailConfig__EmailFrom=""
|
||||
MailConfig__UseSSL="false"
|
||||
MailConfig__Port=587
|
||||
MailConfig__Username=""
|
||||
MailConfig__Password=""
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||
<PackageReference Include="MailKit" Version="4.5.0" />
|
||||
<PackageReference Include="Npgsql" Version="8.0.2" />
|
||||
<PackageReference Include="Npgsql" Version="8.0.3" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace CarCareTracker.Controllers
|
||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
|
||||
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
||||
private readonly ISupplyRecordDataAccess _supplyRecordDataAccess;
|
||||
private readonly IPlanRecordDataAccess _planRecordDataAccess;
|
||||
private readonly IPlanRecordTemplateDataAccess _planRecordTemplateDataAccess;
|
||||
private readonly IUserAccessDataAccess _userAccessDataAccess;
|
||||
private readonly IUserRecordDataAccess _userRecordDataAccess;
|
||||
private readonly IReminderHelper _reminderHelper;
|
||||
@@ -42,6 +45,9 @@ namespace CarCareTracker.Controllers
|
||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||
IUpgradeRecordDataAccess upgradeRecordDataAccess,
|
||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||
ISupplyRecordDataAccess supplyRecordDataAccess,
|
||||
IPlanRecordDataAccess planRecordDataAccess,
|
||||
IPlanRecordTemplateDataAccess planRecordTemplateDataAccess,
|
||||
IUserAccessDataAccess userAccessDataAccess,
|
||||
IUserRecordDataAccess userRecordDataAccess,
|
||||
IMailHelper mailHelper,
|
||||
@@ -60,6 +66,9 @@ namespace CarCareTracker.Controllers
|
||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||
_supplyRecordDataAccess = supplyRecordDataAccess;
|
||||
_planRecordDataAccess = planRecordDataAccess;
|
||||
_planRecordTemplateDataAccess = planRecordTemplateDataAccess;
|
||||
_userAccessDataAccess = userAccessDataAccess;
|
||||
_userRecordDataAccess = userRecordDataAccess;
|
||||
_mailHelper = mailHelper;
|
||||
@@ -95,14 +104,22 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/servicerecords")]
|
||||
public IActionResult ServiceRecords(int vehicleId)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/servicerecords/add")]
|
||||
public IActionResult AddServiceRecord(int vehicleId, ServiceRecordExportModel input)
|
||||
public IActionResult AddServiceRecord(int vehicleId, GenericRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
@@ -131,7 +148,8 @@ namespace CarCareTracker.Controllers
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
Cost = decimal.Parse(input.Cost),
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
|
||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
@@ -163,14 +181,22 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/repairrecords")]
|
||||
public IActionResult RepairRecords(int vehicleId)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/repairrecords/add")]
|
||||
public IActionResult AddRepairRecord(int vehicleId, ServiceRecordExportModel input)
|
||||
public IActionResult AddRepairRecord(int vehicleId, GenericRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
@@ -199,7 +225,8 @@ namespace CarCareTracker.Controllers
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
Cost = decimal.Parse(input.Cost),
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
|
||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
@@ -231,14 +258,22 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/upgraderecords")]
|
||||
public IActionResult UpgradeRecords(int vehicleId)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/upgraderecords/add")]
|
||||
public IActionResult AddUpgradeRecord(int vehicleId, ServiceRecordExportModel input)
|
||||
public IActionResult AddUpgradeRecord(int vehicleId, GenericRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
@@ -267,7 +302,8 @@ namespace CarCareTracker.Controllers
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
Cost = decimal.Parse(input.Cost),
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
|
||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
@@ -299,7 +335,15 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/taxrecords")]
|
||||
public IActionResult TaxRecords(int vehicleId)
|
||||
{
|
||||
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Select(x => new TaxRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields });
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
@@ -332,7 +376,8 @@ namespace CarCareTracker.Controllers
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
Cost = decimal.Parse(input.Cost),
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
|
||||
@@ -353,6 +398,14 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/odometerrecords/latest")]
|
||||
public IActionResult LastOdometer(int vehicleId)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var result = _vehicleLogic.GetMaxMileage(vehicleId);
|
||||
return Json(result);
|
||||
}
|
||||
@@ -361,13 +414,21 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/odometerrecords")]
|
||||
public IActionResult OdometerRecords(int vehicleId)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
//determine if conversion is needed.
|
||||
if (vehicleRecords.All(x => x.InitialMileage == default))
|
||||
{
|
||||
vehicleRecords = _odometerLogic.AutoConvertOdometerRecord(vehicleRecords);
|
||||
}
|
||||
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes });
|
||||
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields });
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
@@ -399,7 +460,8 @@ namespace CarCareTracker.Controllers
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
InitialMileage = (string.IsNullOrWhiteSpace(input.InitialOdometer) || int.Parse(input.InitialOdometer) == default) ? _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()) : int.Parse(input.InitialOdometer),
|
||||
Mileage = int.Parse(input.Odometer)
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
|
||||
@@ -419,6 +481,14 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/gasrecords")]
|
||||
public IActionResult GasRecords(int vehicleId, bool useMPG, bool useUKMPG)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||
var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG)
|
||||
.Select(x => new GasRecordExportModel {
|
||||
@@ -429,7 +499,8 @@ namespace CarCareTracker.Controllers
|
||||
FuelEconomy = x.MilesPerGallon.ToString(),
|
||||
IsFillToFull = x.IsFillToFull.ToString(),
|
||||
MissedFuelUp = x.MissedFuelUp.ToString(),
|
||||
Notes = x.Notes
|
||||
Notes = x.Notes,
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
return Json(result);
|
||||
}
|
||||
@@ -470,7 +541,8 @@ namespace CarCareTracker.Controllers
|
||||
IsFillToFull = bool.Parse(input.IsFillToFull),
|
||||
MissedFuelUp = bool.Parse(input.MissedFuelUp),
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
Cost = decimal.Parse(input.Cost),
|
||||
ExtraFields = input.ExtraFields
|
||||
};
|
||||
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
|
||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
@@ -502,9 +574,17 @@ namespace CarCareTracker.Controllers
|
||||
[Route("/api/vehicle/reminders")]
|
||||
public IActionResult Reminders(int vehicleId)
|
||||
{
|
||||
if (vehicleId == default)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
|
||||
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes});
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString()});
|
||||
return Json(results);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
@@ -562,6 +642,49 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
[Route("/api/cleanup")]
|
||||
public IActionResult CleanUp(bool deepClean = false)
|
||||
{
|
||||
var jsonResponse = new Dictionary<string, string>();
|
||||
//Clear out temp folder
|
||||
var tempFilesDeleted = _fileHelper.ClearTempFolder();
|
||||
jsonResponse.Add("temp_files_deleted", tempFilesDeleted.ToString());
|
||||
if (deepClean)
|
||||
{
|
||||
//clear out unused vehicle thumbnails
|
||||
var vehicles = _dataAccess.GetVehicles();
|
||||
var vehicleImages = vehicles.Select(x => x.ImageLocation).Where(x => x.StartsWith("/images/")).Select(x=>Path.GetFileName(x)).ToList();
|
||||
if (vehicleImages.Any())
|
||||
{
|
||||
var thumbnailsDeleted = _fileHelper.ClearUnlinkedThumbnails(vehicleImages);
|
||||
jsonResponse.Add("unlinked_thumbnails_deleted", thumbnailsDeleted.ToString());
|
||||
}
|
||||
var vehicleDocuments = new List<string>();
|
||||
foreach(Vehicle vehicle in vehicles)
|
||||
{
|
||||
vehicleDocuments.AddRange(_serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y=>Path.GetFileName(y.Location)));
|
||||
vehicleDocuments.AddRange(_collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
|
||||
vehicleDocuments.AddRange(_upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
|
||||
vehicleDocuments.AddRange(_taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
|
||||
vehicleDocuments.AddRange(_gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
|
||||
vehicleDocuments.AddRange(_noteDataAccess.GetNotesByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
|
||||
vehicleDocuments.AddRange(_odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
|
||||
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)));
|
||||
}
|
||||
//shop supplies
|
||||
vehicleDocuments.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
|
||||
if (vehicleDocuments.Any())
|
||||
{
|
||||
var documentsDeleted = _fileHelper.ClearUnlinkedDocuments(vehicleDocuments);
|
||||
jsonResponse.Add("unlinked_documents_deleted", documentsDeleted.ToString());
|
||||
}
|
||||
}
|
||||
return Json(jsonResponse);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
[Route("/api/demo/restore")]
|
||||
public IActionResult RestoreDemo()
|
||||
{
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace CarCareTracker.Controllers
|
||||
LicensePlate = x.LicensePlate,
|
||||
SoldDate = x.SoldDate,
|
||||
IsElectric = x.IsElectric,
|
||||
IsDiesel = x.IsDiesel,
|
||||
UseHours = x.UseHours,
|
||||
ExtraFields = x.ExtraFields,
|
||||
Tags = x.Tags,
|
||||
@@ -219,6 +220,13 @@ namespace CarCareTracker.Controllers
|
||||
var userName = User.Identity.Name;
|
||||
return PartialView("_AccountModal", new UserData() { EmailAddress = emailAddress, UserName = userName });
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
public IActionResult GetRootAccountInformationModal()
|
||||
{
|
||||
var userName = User.Identity.Name;
|
||||
return PartialView("_RootAccountModal", new UserData() { UserName = userName });
|
||||
}
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
|
||||
@@ -34,10 +34,16 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
|
||||
remoteAuthConfig.State = generatedState;
|
||||
var pkceKeyPair = _loginLogic.GetPKCEChallengeCode();
|
||||
remoteAuthConfig.CodeChallenge = pkceKeyPair.Value;
|
||||
if (remoteAuthConfig.ValidateState)
|
||||
{
|
||||
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
|
||||
}
|
||||
if (remoteAuthConfig.UsePKCE)
|
||||
{
|
||||
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
|
||||
}
|
||||
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
|
||||
return Redirect(remoteAuthURL);
|
||||
}
|
||||
@@ -60,10 +66,16 @@ namespace CarCareTracker.Controllers
|
||||
var remoteAuthConfig = _config.GetOpenIDConfig();
|
||||
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
|
||||
remoteAuthConfig.State = generatedState;
|
||||
var pkceKeyPair = _loginLogic.GetPKCEChallengeCode();
|
||||
remoteAuthConfig.CodeChallenge = pkceKeyPair.Value;
|
||||
if (remoteAuthConfig.ValidateState)
|
||||
{
|
||||
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
|
||||
}
|
||||
if (remoteAuthConfig.UsePKCE)
|
||||
{
|
||||
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
|
||||
}
|
||||
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
|
||||
return Json(remoteAuthURL);
|
||||
}
|
||||
@@ -99,6 +111,16 @@ namespace CarCareTracker.Controllers
|
||||
new KeyValuePair<string, string>("client_secret", openIdConfig.ClientSecret),
|
||||
new KeyValuePair<string, string>("redirect_uri", openIdConfig.RedirectURL)
|
||||
};
|
||||
if (openIdConfig.UsePKCE)
|
||||
{
|
||||
//retrieve stored challenge verifier
|
||||
var storedVerifier = Request.Cookies["OIDC_VERIFIER"];
|
||||
if (!string.IsNullOrWhiteSpace(storedVerifier))
|
||||
{
|
||||
httpParams.Add(new KeyValuePair<string, string>("code_verifier", storedVerifier));
|
||||
Response.Cookies.Delete("OIDC_VERIFIER");
|
||||
}
|
||||
}
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Post, openIdConfig.TokenURL)
|
||||
{
|
||||
Content = new FormUrlEncodedContent(httpParams)
|
||||
@@ -137,6 +159,11 @@ namespace CarCareTracker.Controllers
|
||||
} else
|
||||
{
|
||||
_logger.LogInformation("OpenID Provider did not provide a valid id_token");
|
||||
if (!string.IsNullOrWhiteSpace(tokenResult))
|
||||
{
|
||||
//if something was returned from the IdP but it's invalid, we want to log it as an error.
|
||||
_logger.LogError($"Expected id_token, received {tokenResult}");
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
@@ -220,7 +247,7 @@ namespace CarCareTracker.Controllers
|
||||
var result = _loginLogic.ResetPasswordByUser(credentials);
|
||||
return Json(result);
|
||||
}
|
||||
[Authorize] //User must already be logged in to do this.
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))] //User must already be logged in as root user to do this.
|
||||
[HttpPost]
|
||||
public IActionResult CreateLoginCreds(LoginModel credentials)
|
||||
{
|
||||
@@ -235,7 +262,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return Json(false);
|
||||
}
|
||||
[Authorize]
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult DestroyLoginCreds()
|
||||
{
|
||||
|
||||
@@ -219,37 +219,53 @@ namespace CarCareTracker.Controllers
|
||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
||||
if (!Directory.Exists(uploadPath))
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
if (mode == ImportMode.ServiceRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||
if (vehicleRecords.Any())
|
||||
{
|
||||
var exportData = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes, Odometer = x.Mileage.ToString(), Tags = string.Join(" ", x.Tags) });
|
||||
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel {
|
||||
Date = x.Date.ToShortDateString(),
|
||||
Description = x.Description,
|
||||
Cost = x.Cost.ToString("C"),
|
||||
Notes = x.Notes,
|
||||
Odometer = x.Mileage.ToString(),
|
||||
Tags = string.Join(" ", x.Tags),
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
//custom writer
|
||||
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
|
||||
}
|
||||
writer.Dispose();
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
}
|
||||
}
|
||||
else if (mode == ImportMode.RepairRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||
if (vehicleRecords.Any())
|
||||
{
|
||||
var exportData = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes, Odometer = x.Mileage.ToString(), Tags = string.Join(" ", x.Tags) });
|
||||
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel {
|
||||
Date = x.Date.ToShortDateString(),
|
||||
Description = x.Description,
|
||||
Cost = x.Cost.ToString("C"),
|
||||
Notes = x.Notes,
|
||||
Odometer = x.Mileage.ToString(),
|
||||
Tags = string.Join(" ", x.Tags),
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
|
||||
}
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
@@ -257,17 +273,23 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
else if (mode == ImportMode.UpgradeRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||
if (vehicleRecords.Any())
|
||||
{
|
||||
var exportData = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes, Odometer = x.Mileage.ToString(), Tags = string.Join(" ", x.Tags) });
|
||||
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel {
|
||||
Date = x.Date.ToShortDateString(),
|
||||
Description = x.Description,
|
||||
Cost = x.Cost.ToString("C"),
|
||||
Notes = x.Notes,
|
||||
Odometer = x.Mileage.ToString(),
|
||||
Tags = string.Join(" ", x.Tags),
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
|
||||
}
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
@@ -275,17 +297,22 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
else if (mode == ImportMode.OdometerRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
if (vehicleRecords.Any())
|
||||
{
|
||||
var exportData = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), Notes = x.Notes, InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Tags = string.Join(" ", x.Tags) });
|
||||
var exportData = vehicleRecords.Select(x => new OdometerRecordExportModel {
|
||||
Date = x.Date.ToShortDateString(),
|
||||
Notes = x.Notes,
|
||||
InitialOdometer = x.InitialMileage.ToString(),
|
||||
Odometer = x.Mileage.ToString(),
|
||||
Tags = string.Join(" ", x.Tags),
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
StaticHelper.WriteOdometerRecordExportModel(csv, exportData);
|
||||
}
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
@@ -293,8 +320,6 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
else if (mode == ImportMode.SupplyRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
|
||||
if (vehicleRecords.Any())
|
||||
{
|
||||
@@ -307,13 +332,14 @@ namespace CarCareTracker.Controllers
|
||||
PartQuantity = x.Quantity.ToString(),
|
||||
PartSupplier = x.PartSupplier,
|
||||
Notes = x.Notes,
|
||||
Tags = string.Join(" ", x.Tags)
|
||||
Tags = string.Join(" ", x.Tags),
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
StaticHelper.WriteSupplyRecordExportModel(csv, exportData);
|
||||
}
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
@@ -321,17 +347,22 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
else if (mode == ImportMode.TaxRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||
if (vehicleRecords.Any())
|
||||
{
|
||||
var exportData = vehicleRecords.Select(x => new TaxRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes, Tags = string.Join(" ", x.Tags) });
|
||||
var exportData = vehicleRecords.Select(x => new TaxRecordExportModel {
|
||||
Date = x.Date.ToShortDateString(),
|
||||
Description = x.Description,
|
||||
Cost = x.Cost.ToString("C"),
|
||||
Notes = x.Notes,
|
||||
Tags = string.Join(" ", x.Tags),
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
StaticHelper.WriteTaxRecordExportModel(csv, exportData);
|
||||
}
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
@@ -339,8 +370,6 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
else if (mode == ImportMode.PlanRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
|
||||
if (vehicleRecords.Any())
|
||||
{
|
||||
@@ -353,13 +382,14 @@ namespace CarCareTracker.Controllers
|
||||
Type = x.ImportMode.ToString(),
|
||||
Priority = x.Priority.ToString(),
|
||||
Progress = x.Progress.ToString(),
|
||||
Notes = x.Notes
|
||||
Notes = x.Notes,
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
StaticHelper.WritePlanRecordExportModel(csv, exportData);
|
||||
}
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
@@ -367,8 +397,6 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
else if (mode == ImportMode.GasRecord)
|
||||
{
|
||||
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
||||
@@ -383,13 +411,14 @@ namespace CarCareTracker.Controllers
|
||||
IsFillToFull = x.IsFillToFull.ToString(),
|
||||
MissedFuelUp = x.MissedFuelUp.ToString(),
|
||||
Notes = x.Notes,
|
||||
Tags = string.Join(" ", x.Tags)
|
||||
Tags = string.Join(" ", x.Tags),
|
||||
ExtraFields = x.ExtraFields
|
||||
});
|
||||
using (var writer = new StreamWriter(fullExportFilePath))
|
||||
{
|
||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||
{
|
||||
csv.WriteRecords(exportData);
|
||||
StaticHelper.WriteGasRecordExportModel(csv, exportData);
|
||||
}
|
||||
}
|
||||
return Json($"/{fileNameToExport}");
|
||||
@@ -417,7 +446,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
using (var reader = new StreamReader(fullFileName))
|
||||
{
|
||||
var config = new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture);
|
||||
var config = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture);
|
||||
config.MissingFieldFound = null;
|
||||
config.HeaderValidated = null;
|
||||
config.PrepareHeaderForMatch = args => { return args.Header.Trim().ToLower(); };
|
||||
@@ -427,6 +456,7 @@ namespace CarCareTracker.Controllers
|
||||
var records = csv.GetRecords<ImportModel>().ToList();
|
||||
if (records.Any())
|
||||
{
|
||||
var requiredExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)mode).ExtraFields.Where(x=>x.IsRequired).Select(y=>y.Name);
|
||||
foreach (ImportModel importModel in records)
|
||||
{
|
||||
if (mode == ImportMode.GasRecord)
|
||||
@@ -439,7 +469,8 @@ namespace CarCareTracker.Controllers
|
||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
if (string.IsNullOrWhiteSpace(importModel.Cost) && !string.IsNullOrWhiteSpace(importModel.Price))
|
||||
{
|
||||
@@ -495,7 +526,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {importModel.Date}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
_serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
|
||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
@@ -518,7 +550,8 @@ namespace CarCareTracker.Controllers
|
||||
InitialMileage = string.IsNullOrWhiteSpace(importModel.InitialOdometer) ? 0 : decimal.ToInt32(decimal.Parse(importModel.InitialOdometer, NumberStyles.Any)),
|
||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
|
||||
}
|
||||
@@ -537,7 +570,8 @@ namespace CarCareTracker.Controllers
|
||||
Priority = parsedPriority,
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Plan Record on {importModel.DateCreated}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
_planRecordDataAccess.SavePlanRecordToVehicle(convertedRecord);
|
||||
}
|
||||
@@ -551,7 +585,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {importModel.Date}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
|
||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
@@ -575,7 +610,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {importModel.Date}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
|
||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||
@@ -601,7 +637,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = importModel.Description,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Notes = importModel.Notes,
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(convertedRecord);
|
||||
}
|
||||
@@ -614,7 +651,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {importModel.Date}" : importModel.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList()
|
||||
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||
};
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(convertedRecord);
|
||||
}
|
||||
@@ -1787,6 +1825,7 @@ namespace CarCareTracker.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult SaveNoteToVehicleId(Note note)
|
||||
{
|
||||
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
||||
if (result)
|
||||
{
|
||||
@@ -2383,6 +2422,12 @@ namespace CarCareTracker.Controllers
|
||||
#endregion
|
||||
#region "Shared Methods"
|
||||
[HttpPost]
|
||||
public IActionResult GetFilesPendingUpload(List<UploadedFiles> uploadedFiles)
|
||||
{
|
||||
var filesPendingUpload = uploadedFiles.Where(x => x.Location.StartsWith("/temp/")).ToList();
|
||||
return PartialView("_FilesToUpload", filesPendingUpload);
|
||||
}
|
||||
[HttpPost]
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
public IActionResult SearchRecords(int vehicleId, string searchQuery)
|
||||
{
|
||||
|
||||
@@ -147,7 +147,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -141,7 +141,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -176,7 +176,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -198,7 +199,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("userId", userId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -94,7 +94,8 @@ namespace CarCareTracker.External.Implementations
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", userId);
|
||||
return ctext.ExecuteNonQuery() > 0;
|
||||
ctext.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace CarCareTracker.Helper
|
||||
bool RestoreBackup(string fileName, bool clearExisting = false);
|
||||
string MakeAttachmentsExport(List<GenericReportModel> exportData);
|
||||
List<string> GetLanguages();
|
||||
int ClearTempFolder();
|
||||
int ClearUnlinkedThumbnails(List<string> linkedImages);
|
||||
int ClearUnlinkedDocuments(List<string> linkedDocuments);
|
||||
}
|
||||
public class FileHelper : IFileHelper
|
||||
{
|
||||
@@ -314,5 +317,56 @@ namespace CarCareTracker.Helper
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public int ClearTempFolder()
|
||||
{
|
||||
int filesDeleted = 0;
|
||||
var tempPath = GetFullFilePath("temp", false);
|
||||
if (Directory.Exists(tempPath))
|
||||
{
|
||||
var files = Directory.GetFiles(tempPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
File.Delete(file);
|
||||
filesDeleted++;
|
||||
}
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
public int ClearUnlinkedThumbnails(List<string> linkedImages)
|
||||
{
|
||||
int filesDeleted = 0;
|
||||
var imagePath = GetFullFilePath("images", false);
|
||||
if (Directory.Exists(imagePath))
|
||||
{
|
||||
var files = Directory.GetFiles(imagePath);
|
||||
foreach(var file in files)
|
||||
{
|
||||
if (!linkedImages.Contains(Path.GetFileName(file)))
|
||||
{
|
||||
File.Delete(file);
|
||||
filesDeleted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
public int ClearUnlinkedDocuments(List<string> linkedDocuments)
|
||||
{
|
||||
int filesDeleted = 0;
|
||||
var documentPath = GetFullFilePath("documents", false);
|
||||
if (Directory.Exists(documentPath))
|
||||
{
|
||||
var files = Directory.GetFiles(documentPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!linkedDocuments.Contains(Path.GetFileName(file)))
|
||||
{
|
||||
File.Delete(file);
|
||||
filesDeleted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CarCareTracker.Models;
|
||||
using CsvHelper;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
@@ -8,7 +9,7 @@ namespace CarCareTracker.Helper
|
||||
/// </summary>
|
||||
public static class StaticHelper
|
||||
{
|
||||
public static string VersionNumber = "1.3.4";
|
||||
public static string VersionNumber = "1.3.6";
|
||||
public static string DbName = "data/cartracker.db";
|
||||
public static string UserConfigPath = "config/userConfig.json";
|
||||
public static string GenericErrorMessage = "An error occurred, please try again later";
|
||||
@@ -287,5 +288,202 @@ namespace CarCareTracker.Helper
|
||||
return "bi-file-bar-graph";
|
||||
}
|
||||
}
|
||||
//CSV Write Methods
|
||||
public static void WriteGenericRecordExportModel(CsvWriter _csv, IEnumerable<GenericRecordExportModel> genericRecords)
|
||||
{
|
||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||
//write headers
|
||||
_csv.WriteField(nameof(GenericRecordExportModel.Date));
|
||||
_csv.WriteField(nameof(GenericRecordExportModel.Description));
|
||||
_csv.WriteField(nameof(GenericRecordExportModel.Cost));
|
||||
_csv.WriteField(nameof(GenericRecordExportModel.Notes));
|
||||
_csv.WriteField(nameof(GenericRecordExportModel.Odometer));
|
||||
_csv.WriteField(nameof(GenericRecordExportModel.Tags));
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
_csv.WriteField($"extrafield_{extraHeader}");
|
||||
}
|
||||
_csv.NextRecord();
|
||||
foreach (GenericRecordExportModel genericRecord in genericRecords)
|
||||
{
|
||||
_csv.WriteField(genericRecord.Date);
|
||||
_csv.WriteField(genericRecord.Description);
|
||||
_csv.WriteField(genericRecord.Cost);
|
||||
_csv.WriteField(genericRecord.Notes);
|
||||
_csv.WriteField(genericRecord.Odometer);
|
||||
_csv.WriteField(genericRecord.Tags);
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
|
||||
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
|
||||
}
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
public static void WriteOdometerRecordExportModel(CsvWriter _csv, IEnumerable<OdometerRecordExportModel> genericRecords)
|
||||
{
|
||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||
//write headers
|
||||
_csv.WriteField(nameof(OdometerRecordExportModel.Date));
|
||||
_csv.WriteField(nameof(OdometerRecordExportModel.InitialOdometer));
|
||||
_csv.WriteField(nameof(OdometerRecordExportModel.Odometer));
|
||||
_csv.WriteField(nameof(OdometerRecordExportModel.Notes));
|
||||
_csv.WriteField(nameof(OdometerRecordExportModel.Tags));
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
_csv.WriteField($"extrafield_{extraHeader}");
|
||||
}
|
||||
_csv.NextRecord();
|
||||
foreach (OdometerRecordExportModel genericRecord in genericRecords)
|
||||
{
|
||||
_csv.WriteField(genericRecord.Date);
|
||||
_csv.WriteField(genericRecord.InitialOdometer);
|
||||
_csv.WriteField(genericRecord.Odometer);
|
||||
_csv.WriteField(genericRecord.Notes);
|
||||
_csv.WriteField(genericRecord.Tags);
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
|
||||
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
|
||||
}
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
public static void WriteTaxRecordExportModel(CsvWriter _csv, IEnumerable<TaxRecordExportModel> genericRecords)
|
||||
{
|
||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||
//write headers
|
||||
_csv.WriteField(nameof(TaxRecordExportModel.Date));
|
||||
_csv.WriteField(nameof(TaxRecordExportModel.Description));
|
||||
_csv.WriteField(nameof(TaxRecordExportModel.Cost));
|
||||
_csv.WriteField(nameof(TaxRecordExportModel.Notes));
|
||||
_csv.WriteField(nameof(TaxRecordExportModel.Tags));
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
_csv.WriteField($"extrafield_{extraHeader}");
|
||||
}
|
||||
_csv.NextRecord();
|
||||
foreach (TaxRecordExportModel genericRecord in genericRecords)
|
||||
{
|
||||
_csv.WriteField(genericRecord.Date);
|
||||
_csv.WriteField(genericRecord.Description);
|
||||
_csv.WriteField(genericRecord.Cost);
|
||||
_csv.WriteField(genericRecord.Notes);
|
||||
_csv.WriteField(genericRecord.Tags);
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
|
||||
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
|
||||
}
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
public static void WriteSupplyRecordExportModel(CsvWriter _csv, IEnumerable<SupplyRecordExportModel> genericRecords)
|
||||
{
|
||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||
//write headers
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.Date));
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.PartNumber));
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.PartSupplier));
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.PartQuantity));
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.Description));
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.Notes));
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.Cost));
|
||||
_csv.WriteField(nameof(SupplyRecordExportModel.Tags));
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
_csv.WriteField($"extrafield_{extraHeader}");
|
||||
}
|
||||
_csv.NextRecord();
|
||||
foreach (SupplyRecordExportModel genericRecord in genericRecords)
|
||||
{
|
||||
_csv.WriteField(genericRecord.Date);
|
||||
_csv.WriteField(genericRecord.PartNumber);
|
||||
_csv.WriteField(genericRecord.PartSupplier);
|
||||
_csv.WriteField(genericRecord.PartQuantity);
|
||||
_csv.WriteField(genericRecord.Description);
|
||||
_csv.WriteField(genericRecord.Notes);
|
||||
_csv.WriteField(genericRecord.Cost);
|
||||
_csv.WriteField(genericRecord.Tags);
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
|
||||
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
|
||||
}
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
public static void WritePlanRecordExportModel(CsvWriter _csv, IEnumerable<PlanRecordExportModel> genericRecords)
|
||||
{
|
||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||
//write headers
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.DateCreated));
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.DateModified));
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.Description));
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.Notes));
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.Type));
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.Priority));
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.Progress));
|
||||
_csv.WriteField(nameof(PlanRecordExportModel.Cost));
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
_csv.WriteField($"extrafield_{extraHeader}");
|
||||
}
|
||||
_csv.NextRecord();
|
||||
foreach (PlanRecordExportModel genericRecord in genericRecords)
|
||||
{
|
||||
_csv.WriteField(genericRecord.DateCreated);
|
||||
_csv.WriteField(genericRecord.DateModified);
|
||||
_csv.WriteField(genericRecord.Description);
|
||||
_csv.WriteField(genericRecord.Notes);
|
||||
_csv.WriteField(genericRecord.Type);
|
||||
_csv.WriteField(genericRecord.Priority);
|
||||
_csv.WriteField(genericRecord.Progress);
|
||||
_csv.WriteField(genericRecord.Cost);
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
|
||||
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
|
||||
}
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords)
|
||||
{
|
||||
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
|
||||
//write headers
|
||||
_csv.WriteField(nameof(GasRecordExportModel.Date));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.Odometer));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.FuelConsumed));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.Cost));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.FuelEconomy));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.IsFillToFull));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.MissedFuelUp));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.Notes));
|
||||
_csv.WriteField(nameof(GasRecordExportModel.Tags));
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
_csv.WriteField($"extrafield_{extraHeader}");
|
||||
}
|
||||
_csv.NextRecord();
|
||||
foreach (GasRecordExportModel genericRecord in genericRecords)
|
||||
{
|
||||
_csv.WriteField(genericRecord.Date);
|
||||
_csv.WriteField(genericRecord.Odometer);
|
||||
_csv.WriteField(genericRecord.FuelConsumed);
|
||||
_csv.WriteField(genericRecord.Cost);
|
||||
_csv.WriteField(genericRecord.FuelEconomy);
|
||||
_csv.WriteField(genericRecord.IsFillToFull);
|
||||
_csv.WriteField(genericRecord.MissedFuelUp);
|
||||
_csv.WriteField(genericRecord.Notes);
|
||||
_csv.WriteField(genericRecord.Tags);
|
||||
foreach (string extraHeader in extraHeaders)
|
||||
{
|
||||
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
|
||||
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
|
||||
}
|
||||
_csv.NextRecord();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -28,7 +29,7 @@ namespace CarCareTracker.Logic
|
||||
bool GenerateTokenForEmailAddress(string emailAddress, bool isPasswordReset);
|
||||
List<UserData> GetAllUsers();
|
||||
List<Token> GetAllTokens();
|
||||
|
||||
KeyValuePair<string, string> GetPKCEChallengeCode();
|
||||
}
|
||||
public class LoginLogic : ILoginLogic
|
||||
{
|
||||
@@ -439,6 +440,14 @@ namespace CarCareTracker.Logic
|
||||
{
|
||||
return Guid.NewGuid().ToString().Substring(0, 8);
|
||||
}
|
||||
public KeyValuePair<string, string> GetPKCEChallengeCode()
|
||||
{
|
||||
var verifierCode = Base64UrlEncoder.Encode(Guid.NewGuid().ToString().Replace("-", ""));
|
||||
var verifierBytes = Encoding.UTF8.GetBytes(verifierCode);
|
||||
var hashedCode = SHA256.Create().ComputeHash(verifierBytes);
|
||||
var encodedChallengeCode = Base64UrlEncoder.Encode(hashedCode);
|
||||
return new KeyValuePair<string, string>(verifierCode, encodedChallengeCode);
|
||||
}
|
||||
public bool GenerateTokenForEmailAddress(string emailAddress, bool isPasswordReset)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
@@ -27,6 +27,18 @@ namespace CarCareTracker.MapProfile
|
||||
Map(m => m.Type).Name(["type"]);
|
||||
Map(m => m.Priority).Name(["priority"]);
|
||||
Map(m => m.Tags).Name(["tags"]);
|
||||
Map(m => m.ExtraFields).Convert(row =>
|
||||
{
|
||||
var attributes = new Dictionary<string, string>();
|
||||
foreach (var header in row.Row.HeaderRecord)
|
||||
{
|
||||
if (header.ToLower().StartsWith("extrafield_"))
|
||||
{
|
||||
attributes.Add(header.Substring(11), row.Row.GetField(header));
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
{
|
||||
public string EmailServer { get; set; }
|
||||
public string EmailFrom { get; set; }
|
||||
public bool UseSSL { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
|
||||
@@ -10,9 +10,18 @@
|
||||
public string RedirectURL { get; set; }
|
||||
public string Scope { get; set; }
|
||||
public string State { get; set; }
|
||||
public string CodeChallenge { get; set; }
|
||||
public bool ValidateState { get; set; } = false;
|
||||
public bool DisableRegularLogin { get; set; } = false;
|
||||
public bool UsePKCE { get; set; } = false;
|
||||
public string LogOutURL { get; set; } = "";
|
||||
public string RemoteAuthURL { get { return $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}"; } }
|
||||
public string RemoteAuthURL { get {
|
||||
var redirectUrl = $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}";
|
||||
if (UsePKCE)
|
||||
{
|
||||
redirectUrl += $"&code_challenge={CodeChallenge}&code_challenge_method=S256";
|
||||
}
|
||||
return redirectUrl;
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
public string PartSupplier { get; set; }
|
||||
public string PartQuantity { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public Dictionary<string,string> ExtraFields {get;set;}
|
||||
}
|
||||
|
||||
public class SupplyRecordExportModel
|
||||
@@ -37,9 +38,9 @@
|
||||
public string Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceRecordExportModel
|
||||
public class GenericRecordExportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
@@ -47,6 +48,7 @@
|
||||
public string Notes { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
}
|
||||
public class OdometerRecordExportModel
|
||||
{
|
||||
@@ -55,6 +57,7 @@
|
||||
public string Odometer { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
}
|
||||
public class TaxRecordExportModel
|
||||
{
|
||||
@@ -63,6 +66,7 @@
|
||||
public string Notes { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
}
|
||||
public class GasRecordExportModel
|
||||
{
|
||||
@@ -75,6 +79,7 @@
|
||||
public string MissedFuelUp { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
}
|
||||
public class ReminderExportModel
|
||||
{
|
||||
@@ -82,6 +87,8 @@
|
||||
public string Urgency { get; set; }
|
||||
public string Metric { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string DueDate { get; set; }
|
||||
public string DueOdometer { get; set; }
|
||||
}
|
||||
public class PlanRecordExportModel
|
||||
{
|
||||
@@ -93,6 +100,6 @@
|
||||
public string Priority { get; set; }
|
||||
public string Progress { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public List<ExtraField> ExtraFields { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
public decimal PurchasePrice { get; set; }
|
||||
public decimal SoldPrice { get; set; }
|
||||
public bool IsElectric { get; set; } = false;
|
||||
public bool IsDiesel { get; set; } = false;
|
||||
public bool UseHours { get; set; } = false;
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
public string LicensePlate { get; set; }
|
||||
public string SoldDate { get; set; }
|
||||
public bool IsElectric { get; set; } = false;
|
||||
public bool IsDiesel { get; set; } = false;
|
||||
public bool UseHours { get; set; } = false;
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
|
||||
@@ -22,6 +22,9 @@ LubeLogger is available as both a Docker Image and a Windows Standalone Executab
|
||||
|
||||
Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started) on how to download either of them
|
||||
|
||||
### Kubernetes Deployment
|
||||
[Helm Chart](https://artifacthub.io/packages/helm/anza-labs/lubelogger) provided by [Anza-Labs](https://github.com/anza-labs)
|
||||
|
||||
### Need Help?
|
||||
[Documentation](https://docs.lubelogger.com/)
|
||||
|
||||
|
||||
@@ -40,6 +40,55 @@
|
||||
No Params
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Returns a list of odometer records for the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/latest</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Returns last reported odometer for the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Odometer Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
initialOdometer - Initial Odometer reading(optional)<br />
|
||||
odometer - Odometer reading<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
@@ -270,56 +319,22 @@
|
||||
No Params(must be root user)
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/cleanup</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Clears out temp files. Deep clean will also delete unlinked thumbnails and documents. Returns number of deleted files.
|
||||
</div>
|
||||
<div class="col-3">
|
||||
(must be root user)<br />
|
||||
deepClean(bool) - Perform deep clean
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Returns a list of odometer records for the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/latest</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Returns last reported odometer for the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5 copyable">
|
||||
<code>/api/vehicle/odometerrecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Odometer Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
initialOdometer - Initial Odometer reading(optional)<br />
|
||||
odometer - Odometer reading<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('.copyable').on('click', function (e) {
|
||||
copyToClipboard(e.currentTarget);
|
||||
|
||||
@@ -41,7 +41,12 @@
|
||||
<a class="dropdown-item" href="/Admin"><span class="display-3 ms-2"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</span></a>
|
||||
</li>
|
||||
}
|
||||
@if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<li>
|
||||
<button class="nav-link" onclick="showRootAccountInformationModal()"><span class="display-3 ms-2"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</span></button>
|
||||
</li>
|
||||
} else
|
||||
{
|
||||
<li>
|
||||
<button class="nav-link" onclick="showAccountInformationModal()"><span class="display-3 ms-2"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</span></button>
|
||||
@@ -90,7 +95,12 @@
|
||||
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</a>
|
||||
</li>
|
||||
}
|
||||
@if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<li>
|
||||
<button class="dropdown-item" onclick="showRootAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
|
||||
</li>
|
||||
} else
|
||||
{
|
||||
<li>
|
||||
<button class="dropdown-item" onclick="showAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addVehicleModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
|
||||
<h5 class="modal-title" id="updateAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
26
Views/Home/_RootAccountModal.cshtml
Normal file
26
Views/Home/_RootAccountModal.cshtml
Normal file
@@ -0,0 +1,26 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model UserData
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="updateRootAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label for="inputUsername">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUsername" class="form-control" placeholder="@translator.Translate(userLanguage, "Account Username")" value="@Model.UserName">
|
||||
<label for="inputPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "Password")" value="">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAccountInformationModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
<button type="button" onclick="validateAndSaveRootUserAccount()" class="btn btn-primary">@translator.Translate(userLanguage, "Update")</button>
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('collisionRecordMileage')">+</button>
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('collisionRecordMileage')"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -89,10 +89,9 @@
|
||||
}
|
||||
<label for="collisionRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="collisionRecordFiles">
|
||||
<br />
|
||||
|
||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="collisionRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="collisionRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'collisionRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="collisionRecordModalContent">
|
||||
</div>
|
||||
|
||||
23
Views/Vehicle/_FilesToUpload.cshtml
Normal file
23
Views/Vehicle/_FilesToUpload.cshtml
Normal file
@@ -0,0 +1,23 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model List<UploadedFiles>
|
||||
<label id="documentsPendingUploadLabel">@translator.Translate(userLanguage, "Documents Pending Upload")</label>
|
||||
<ul class="list-group" id="documentsPendingUploadList">
|
||||
@foreach (UploadedFiles filesUploaded in Model)
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -221,7 +221,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="gasRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="gasRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'gasRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="gasRecordModalContent">
|
||||
</div>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('gasRecordMileage')">+</button>
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('gasRecordMileage')"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -121,6 +121,7 @@
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="gasRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<br />
|
||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="noteRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="noteModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="noteModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'noteFiles')">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content" id="noteModalContent">
|
||||
</div>
|
||||
|
||||
@@ -23,14 +23,22 @@
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="initialOdometerRecordMileage">@translator.Translate(userLanguage, "Initial Odometer")</label>
|
||||
<input type="number" inputmode="numeric" id="initialOdometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Initial Odometer reading")" value="@(Model.InitialMileage)">
|
||||
<div class="input-group">
|
||||
<input type="number" inputmode="numeric" id="initialOdometerRecordMileage" @(Model.InitialMileage != default ? "disabled" : "") class="form-control" placeholder="@translator.Translate(userLanguage,"Initial Odometer reading")" value="@(Model.InitialMileage)">
|
||||
@if (Model.InitialMileage != default)
|
||||
{
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-secondary zero-y-padding" onclick="toggleInitialOdometerEnabled()"><i class="bi bi-pencil"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<label for="odometerRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<div class="input-group">
|
||||
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('odometerRecordMileage')">+</button>
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('odometerRecordMileage')"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -68,6 +76,7 @@
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="odometerRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="odometerRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="odometerRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'odometerRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="odometerRecordModalContent">
|
||||
</div>
|
||||
|
||||
@@ -73,10 +73,9 @@
|
||||
{
|
||||
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="planRecordFiles">
|
||||
<br />
|
||||
|
||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,10 +65,9 @@
|
||||
{
|
||||
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="planRecordFiles">
|
||||
<br />
|
||||
|
||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -97,14 +97,14 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="planRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="planRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'planRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="planRecordModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="planRecordTemplateModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="planRecordTemplateModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'planRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="planRecordTemplateModalContent">
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<input type="text" id="reminderDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Reminder Description")" value="@Model.Description">
|
||||
<label>@translator.Translate(userLanguage,"Remind me on")</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricDate" value="@(ReminderMetric.Date)" checked="@(Model.Metric == ReminderMetric.Date)">
|
||||
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricDate" value="@(ReminderMetric.Date)" checked="@(Model.Metric == ReminderMetric.Date)">
|
||||
<label class="form-check-label" for="reminderMetricDate">@translator.Translate(userLanguage,"Date")</label>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
@@ -29,7 +29,7 @@
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricOdometer" value="@(ReminderMetric.Odometer)" checked="@(Model.Metric == ReminderMetric.Odometer)">
|
||||
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricOdometer" value="@(ReminderMetric.Odometer)" checked="@(Model.Metric == ReminderMetric.Odometer)">
|
||||
<label class="form-check-label" for="reminderMetricOdometer">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
@@ -37,12 +37,12 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('reminderMileage')">+</button>
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('reminderMileage')"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricBoth" value="@(ReminderMetric.Both)" checked="@(Model.Metric == ReminderMetric.Both)">
|
||||
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricBoth" value="@(ReminderMetric.Both)" checked="@(Model.Metric == ReminderMetric.Both)">
|
||||
<label class="form-check-label" for="reminderMetricBoth">@translator.Translate(userLanguage,"Whichever comes first")</label>
|
||||
</div>
|
||||
<div class="d-grid"></div>
|
||||
@@ -62,7 +62,7 @@
|
||||
<label class="form-check-label" for="reminderIsRecurring">@translator.Translate(userLanguage,"Is Recurring")</label>
|
||||
</div>
|
||||
<label for="reminderRecurringMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<select class="form-select" onchange="checkCustomMileageInterval()" id="reminderRecurringMileage" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<select class="form-select" onchange="checkCustomMileageInterval()" id="reminderRecurringMileage" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Odometer || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
|
||||
<!option value="Other" @(Model.ReminderMileageInterval == ReminderMileageInterval.Other ? "selected" : "")>@(Model.ReminderMileageInterval == ReminderMileageInterval.Other && Model.CustomMileageInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMileageInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
|
||||
<!option value="FiftyMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyMiles ? "selected" : "")>50 mi. / Km</!option>
|
||||
<!option value="OneHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredMiles ? "selected" : "")>100 mi. / Km</!option>
|
||||
@@ -82,8 +82,8 @@
|
||||
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
|
||||
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
|
||||
</select>
|
||||
<label for="reminderRecurringMonth">Month</label>
|
||||
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<label for="reminderRecurringMonth">@translator.Translate(userLanguage, "Month")</label>
|
||||
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Date || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
|
||||
<!option value="Other" @(Model.ReminderMonthInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.ReminderMonthInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
|
||||
<!option value="OneMonth" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage, "1 Month")</!option>
|
||||
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage,"3 Months")</!option>
|
||||
|
||||
@@ -73,18 +73,22 @@
|
||||
{
|
||||
<td class="col-1"><span class="text-success d-inline-block d-md-none"><i class="bi bi-hourglass-top h3"></i></span><span class="badge text-bg-success d-none d-md-inline-block">@translator.Translate(userLanguage, "Not Urgent")</span></td>
|
||||
}
|
||||
@if (reminderRecord.Metric == ReminderMetric.Date)
|
||||
{
|
||||
<td class="col-2">@reminderRecord.Date.ToShortDateString()</td>
|
||||
}
|
||||
else if (reminderRecord.Metric == ReminderMetric.Odometer)
|
||||
{
|
||||
<td class="col-2">@reminderRecord.Mileage</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="col-2">@reminderRecord.Metric</td>
|
||||
}
|
||||
<td class="col-2">
|
||||
<span data-column="metric" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-html="true" data-bs-title="@($"<b><i class='bi bi-calendar-event me-2'></i></b>{reminderRecord.Date.ToShortDateString()}<b class='ms-2'><i class='bi bi-speedometer me-2'></i></b>{reminderRecord.Mileage}")">
|
||||
@if (reminderRecord.Metric == ReminderMetric.Date)
|
||||
{
|
||||
@reminderRecord.Date.ToShortDateString()
|
||||
}
|
||||
else if (reminderRecord.Metric == ReminderMetric.Odometer)
|
||||
{
|
||||
@reminderRecord.Mileage
|
||||
}
|
||||
else
|
||||
{
|
||||
@reminderRecord.Metric
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
<td class="@(hasRefresh ? "col-3 col-md-4" : "col-5")" data-record-type='cost'>@reminderRecord.Description</td>
|
||||
<td class="col-2 col-md-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
|
||||
@if (hasRefresh)
|
||||
@@ -118,4 +122,8 @@
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
$("[data-column='metric']").map((x, y) => new bootstrap.Tooltip(y));
|
||||
</script>
|
||||
@@ -28,7 +28,7 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('serviceRecordMileage')">+</button>
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('serviceRecordMileage')"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -91,6 +91,7 @@
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="serviceRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="serviceRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="serviceRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'serviceRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="serviceRecordModalContent">
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="input-group">
|
||||
<input type="text" inputmode="decimal" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"Quantity")" value="@(isNew ? "1" : Model.Quantity)">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm zero-y-padding btn-primary" onclick="replenishSupplies()">+</button>
|
||||
<button type="button" class="btn btn-sm zero-y-padding btn-primary" onclick="replenishSupplies()"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,6 +77,7 @@
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="supplyRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="supplyRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="supplyRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'supplyRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="supplyRecordModalContent">
|
||||
</div>
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="taxRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="taxRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="taxRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'taxRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="taxRecordModalContent">
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('upgradeRecordMileage')">+</button>
|
||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('upgradeRecordMileage')"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -89,10 +89,9 @@
|
||||
}
|
||||
<label for="upgradeRecordFiles">Upload documents(optional)</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="upgradeRecordFiles">
|
||||
<br />
|
||||
|
||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
}
|
||||
<div id="filesPendingUpload"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-2 flex-grow-1 col-xl-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
|
||||
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
|
||||
<th scope="col" class="col-3 flex-grow-1 col-xl-4" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 col-xl-4" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('upgrade-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
@@ -124,7 +124,7 @@
|
||||
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@upgradeRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditUpgradeRecordModal,@upgradeRecord.Id)" data-tags='@string.Join(" ", upgradeRecord.Tags)'>
|
||||
<td class="col-2 flex-grow-1 col-xl-1" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(upgradeRecord.Date)">@upgradeRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@upgradeRecord.Mileage</td>
|
||||
<td class="col-3 flex-grow-1 col-xl-4" data-column="description">@upgradeRecord.Description</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 col-xl-4" data-column="description">@upgradeRecord.Description</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && upgradeRecord.Cost == default) ? "---" : upgradeRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
@@ -150,7 +150,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="upgradeRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="upgradeRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event, 'upgradeRecordFiles')">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="upgradeRecordModalContent">
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model List<UploadedFiles>
|
||||
<label>@translator.Translate(userLanguage, "Uploaded Documents")</label>
|
||||
<ul class="list-group">
|
||||
<label id="uploadedDocumentsLabel">@translator.Translate(userLanguage, "Uploaded Documents")</label>
|
||||
<ul class="list-group" id="uploadedDocumentsList">
|
||||
@foreach (UploadedFiles filesUploaded in Model)
|
||||
{
|
||||
<li class="list-group-item">
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
{
|
||||
<span><i class="bi bi-ev-station me-2"></i>@translator.Translate(userLanguage, "Electric")</span>
|
||||
}
|
||||
else if (Model.VehicleData.IsDiesel)
|
||||
{
|
||||
<span><i class="bi bi-fuel-pump-diesel me-2"></i>@translator.Translate(userLanguage, "Diesel")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span><i class="bi bi-fuel-pump me-2"></i>@translator.Translate(userLanguage, "Gasoline")</span>
|
||||
|
||||
@@ -46,10 +46,12 @@
|
||||
}
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="inputIsElectric" checked="@Model.IsElectric">
|
||||
<label class="form-check-label" for="inputIsElectric">@translator.Translate(userLanguage, "Electric Vehicle")</label>
|
||||
</div>
|
||||
<label for="inputFuelType">@translator.Translate(userLanguage, "Fuel Type")</label>
|
||||
<select class="form-select" onchange="checkCustomMonthInterval()" id="inputFuelType")>
|
||||
<!option value="Gasoline" @(!Model.IsDiesel && !Model.IsElectric ? "selected" : "")>@translator.Translate(userLanguage, "Gasoline")</!option>
|
||||
<!option value="Diesel" @(Model.IsDiesel ? "selected" : "")>@translator.Translate(userLanguage, "Diesel")</!option>
|
||||
<!option value="Electric" @(Model.IsElectric ? "selected" : "")>@translator.Translate(userLanguage, "Electric")</!option>
|
||||
</select>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="inputUseHours" checked="@Model.UseHours">
|
||||
<label class="form-check-label" for="inputUseHours">@translator.Translate(userLanguage, "Use Engine Hours")</label>
|
||||
|
||||
@@ -30,6 +30,7 @@ services:
|
||||
POSTGRES_PASSWORD: "lubepass"
|
||||
POSTGRES_DB: "lubelogger"
|
||||
volumes:
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- postgres:/var/lib/postgresql/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
|
||||
|
||||
6
init.sql
Normal file
6
init.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'app') THEN
|
||||
CREATE SCHEMA app;
|
||||
END IF;
|
||||
END $$;
|
||||
File diff suppressed because one or more lines are too long
BIN
wwwroot/defaults/lubelogger_maskable_icon_128.png
Normal file
BIN
wwwroot/defaults/lubelogger_maskable_icon_128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
BIN
wwwroot/defaults/lubelogger_maskable_icon_192.png
Normal file
BIN
wwwroot/defaults/lubelogger_maskable_icon_192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
wwwroot/defaults/lubelogger_maskable_icon_72.png
Normal file
BIN
wwwroot/defaults/lubelogger_maskable_icon_72.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -341,6 +341,47 @@ function showAccountInformationModal() {
|
||||
$('#accountInformationModal').modal('show');
|
||||
})
|
||||
}
|
||||
|
||||
function showRootAccountInformationModal() {
|
||||
$.get('/Home/GetRootAccountInformationModal', function (data) {
|
||||
$('#accountInformationModalContent').html(data);
|
||||
$('#accountInformationModal').modal('show');
|
||||
})
|
||||
}
|
||||
function validateAndSaveRootUserAccount() {
|
||||
var hasError = false;
|
||||
if ($('#inputUsername').val().trim() == '') {
|
||||
$('#inputUsername').addClass("is-invalid");
|
||||
hasError = true;
|
||||
} else {
|
||||
$('#inputUsername').removeClass("is-invalid");
|
||||
}
|
||||
if ($('#inputPassword').val().trim() == '') {
|
||||
$('#inputPassword').addClass("is-invalid");
|
||||
hasError = true;
|
||||
} else {
|
||||
$('#inputPassword').removeClass("is-invalid");
|
||||
}
|
||||
if (hasError) {
|
||||
errorToast("Please check the form data");
|
||||
return;
|
||||
}
|
||||
var userAccountInfo = {
|
||||
userName: $('#inputUsername').val(),
|
||||
password: $('#inputPassword').val()
|
||||
}
|
||||
$.post('/Login/CreateLoginCreds', { credentials: userAccountInfo }, function (data) {
|
||||
if (data) {
|
||||
//hide modal
|
||||
hideAccountInformationModal();
|
||||
successToast('Root Account Updated');
|
||||
performLogOut();
|
||||
} else {
|
||||
errorToast(data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function hideAccountInformationModal() {
|
||||
$('#accountInformationModal').modal('hide');
|
||||
}
|
||||
|
||||
@@ -208,4 +208,12 @@ function saveMultipleOdometerRecordsToVehicle() {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
function toggleInitialOdometerEnabled() {
|
||||
if ($("#initialOdometerRecordMileage").prop("disabled")) {
|
||||
$("#initialOdometerRecordMileage").prop("disabled", false);
|
||||
} else {
|
||||
$("#initialOdometerRecordMileage").prop("disabled", true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -137,8 +137,20 @@ function appendMileageToOdometer(increment) {
|
||||
function enableRecurring() {
|
||||
var reminderIsRecurring = $("#reminderIsRecurring").is(":checked");
|
||||
if (reminderIsRecurring) {
|
||||
$("#reminderRecurringMileage").attr('disabled', false);
|
||||
$("#reminderRecurringMonth").attr('disabled', false);
|
||||
//check selected metric
|
||||
var reminderMetric = $('#reminderOptions input:radio:checked').val();
|
||||
if (reminderMetric == "Date") {
|
||||
$("#reminderRecurringMonth").attr('disabled', false);
|
||||
$("#reminderRecurringMileage").attr('disabled', true);
|
||||
}
|
||||
else if (reminderMetric == "Odometer") {
|
||||
$("#reminderRecurringMileage").attr('disabled', false);
|
||||
$("#reminderRecurringMonth").attr('disabled', true);
|
||||
}
|
||||
else if (reminderMetric == "Both") {
|
||||
$("#reminderRecurringMonth").attr('disabled', false);
|
||||
$("#reminderRecurringMileage").attr('disabled', false);
|
||||
}
|
||||
} else {
|
||||
$("#reminderRecurringMileage").attr('disabled', true);
|
||||
$("#reminderRecurringMonth").attr('disabled', true);
|
||||
|
||||
@@ -40,7 +40,8 @@ function saveVehicle(isEdit) {
|
||||
var vehiclePurchaseDate = $("#inputPurchaseDate").val();
|
||||
var vehicleSoldDate = $("#inputSoldDate").val();
|
||||
var vehicleLicensePlate = $("#inputLicensePlate").val();
|
||||
var vehicleIsElectric = $("#inputIsElectric").is(":checked");
|
||||
var vehicleIsElectric = $("#inputFuelType").val() == 'Electric';
|
||||
var vehicleIsDiesel = $("#inputFuelType").val() == 'Diesel';
|
||||
var vehicleUseHours = $("#inputUseHours").is(":checked");
|
||||
var vehicleHasOdometerAdjustment = $("#inputHasOdometerAdjustment").is(':checked');
|
||||
var vehicleOdometerMultiplier = $("#inputOdometerMultiplier").val();
|
||||
@@ -119,6 +120,7 @@ function saveVehicle(isEdit) {
|
||||
model: vehicleModel,
|
||||
licensePlate: vehicleLicensePlate,
|
||||
isElectric: vehicleIsElectric,
|
||||
isDiesel: vehicleIsDiesel,
|
||||
tags: vehicleTags,
|
||||
useHours: vehicleUseHours,
|
||||
extraFields: extraFields.extraFields,
|
||||
@@ -178,8 +180,8 @@ function uploadFileAsync(event) {
|
||||
});
|
||||
}
|
||||
function isValidMoney(input) {
|
||||
const euRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}(\.?\d{3})?(,\d{1,3}?)?\)?$/;
|
||||
const usRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}(,?\d{3})?(\.\d{1,3}?)?\)?$/;
|
||||
const euRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}((\.\d{3}){0,8}|(\d{3}){0,8})(,\d{1,3}?)?\)?$/;
|
||||
const usRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}((,\d{3}){0,8}|(\d{3}){0,8})(\.\d{1,3}?)?\)?$/;
|
||||
return (euRegex.test(input) || usRegex.test(input));
|
||||
}
|
||||
function initDatePicker(input, futureOnly) {
|
||||
@@ -373,8 +375,12 @@ function uploadVehicleFilesAsync(event) {
|
||||
type: 'POST',
|
||||
success: function (response) {
|
||||
sloader.hide();
|
||||
$(event).val(""); //clear out the filename from the uploader
|
||||
if (response.length > 0) {
|
||||
uploadedFiles.push.apply(uploadedFiles, response);
|
||||
$.post('/Vehicle/GetFilesPendingUpload', { uploadedFiles: uploadedFiles }, function (viewData) {
|
||||
$("#filesPendingUpload").html(viewData);
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
@@ -386,6 +392,15 @@ function uploadVehicleFilesAsync(event) {
|
||||
function deleteFileFromUploadedFiles(fileLocation, event) {
|
||||
event.parentElement.parentElement.parentElement.remove();
|
||||
uploadedFiles = uploadedFiles.filter(x => x.location != fileLocation);
|
||||
if (fileLocation.startsWith("/temp/")) {
|
||||
if ($("#documentsPendingUploadList > li").length == 0) {
|
||||
$("#documentsPendingUploadLabel").text("");
|
||||
}
|
||||
} else if (fileLocation.startsWith("/documents/")) {
|
||||
if ($("#uploadedDocumentsList > li").length == 0) {
|
||||
$("#uploadedDocumentsLabel").text("");
|
||||
}
|
||||
}
|
||||
}
|
||||
function editFileName(fileLocation, event) {
|
||||
Swal.fire({
|
||||
@@ -1053,4 +1068,24 @@ function bindModalInputChanges(modalName) {
|
||||
$(`#${modalName} select, #${modalName} input[type='checkbox']`).off('input').on('input', function (e) {
|
||||
$(e.currentTarget).attr('data-changed', true);
|
||||
});
|
||||
}
|
||||
function handleModalPaste(e, recordType) {
|
||||
var clipboardFiles = e.clipboardData.files;
|
||||
var acceptableFileFormats = $(`#${recordType}`).attr("accept");
|
||||
var acceptableFileFormatsArray = acceptableFileFormats.split(',');
|
||||
var acceptableFiles = new DataTransfer();
|
||||
if (clipboardFiles.length > 0) {
|
||||
for (var x = 0; x < clipboardFiles.length; x++) {
|
||||
if (acceptableFileFormats != "*") {
|
||||
var fileExtension = `.${clipboardFiles[x].name.split('.').pop()}`;
|
||||
if (acceptableFileFormatsArray.includes(fileExtension)) {
|
||||
acceptableFiles.items.add(clipboardFiles[x]);
|
||||
}
|
||||
} else {
|
||||
acceptableFiles.items.add(clipboardFiles[x]);
|
||||
}
|
||||
}
|
||||
$(`#${recordType}`)[0].files = acceptableFiles.files;
|
||||
$(`#${recordType}`).trigger('change');
|
||||
}
|
||||
}
|
||||
@@ -341,14 +341,14 @@
|
||||
break;
|
||||
//COPY EVENT
|
||||
case 67:
|
||||
if (event.ctrlKey) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
event.preventDefault();
|
||||
navigator.clipboard.writeText(self.itemsArray.join(" "));
|
||||
}
|
||||
break;
|
||||
//PASTE EVENT
|
||||
case 86:
|
||||
if (event.ctrlKey) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
setTimeout(function () {
|
||||
var pastedString = $input.val();
|
||||
//clear pasted string.
|
||||
|
||||
@@ -6,22 +6,44 @@
|
||||
{
|
||||
"src": "/defaults/lubelogger_icon_72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/defaults/lubelogger_maskable_icon_72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/defaults/lubelogger_icon_128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/defaults/lubelogger_maskable_icon_128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/defaults/lubelogger_icon_144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/defaults/lubelogger_icon_192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/defaults/lubelogger_maskable_icon_192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"screenshots": [
|
||||
|
||||
Reference in New Issue
Block a user