Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e4e2795b6 | ||
|
|
d4e51b714d | ||
|
|
6a164dc60b | ||
|
|
ce602dcf66 | ||
|
|
2facb1ab46 | ||
|
|
3c7d575c85 | ||
|
|
9635c3c2c5 | ||
|
|
72427fc19d | ||
|
|
0bbd3c5491 | ||
|
|
871de4e75a | ||
|
|
bf984f280e | ||
|
|
2b8f3cf13a | ||
|
|
1630a5c9ec | ||
|
|
f17faa33f4 | ||
|
|
c245b848a0 | ||
|
|
0ead9112c6 | ||
|
|
44da393369 | ||
|
|
5ae1628b7c | ||
|
|
59511d9ddd | ||
|
|
82b0fba99a | ||
|
|
618107e515 | ||
|
|
35f931adf2 | ||
|
|
4a1e41cd54 | ||
|
|
888ec5cbbe | ||
|
|
f8f044d0cc | ||
|
|
36148c5539 | ||
|
|
030dde0d64 | ||
|
|
99faa951f9 | ||
|
|
7fb3a80ea3 | ||
|
|
f277048aa9 | ||
|
|
34e3b8e145 | ||
|
|
91e8526b8e | ||
|
|
926188ef64 | ||
|
|
1b5fc6729e | ||
|
|
3ac53ea144 | ||
|
|
4dc3b4f741 | ||
|
|
7c1e6bd45c | ||
|
|
00b3145e79 | ||
|
|
def9a7770f | ||
|
|
5ba0bd5771 | ||
|
|
adc2de07f7 | ||
|
|
e12f528635 | ||
|
|
c96ac53b91 | ||
|
|
8c15bd5534 | ||
|
|
90755d68b3 | ||
|
|
94dfe9a0bb | ||
|
|
9dbcf71856 | ||
|
|
728fc3593f | ||
|
|
1347585af2 | ||
|
|
920de489be | ||
|
|
19cb964543 | ||
|
|
eb03979ad8 | ||
|
|
ebe871b82a | ||
|
|
04a103fdc0 | ||
|
|
d0f80d4150 | ||
|
|
48c388dea7 | ||
|
|
d05f8b1f84 | ||
|
|
167d6e607e | ||
|
|
8755c6379a | ||
|
|
1e2bd0ea4b | ||
|
|
2cf93cf669 | ||
|
|
a18b81d52e | ||
|
|
604c98a031 | ||
|
|
940f48b816 | ||
|
|
f81545178d | ||
|
|
a87b599bdf | ||
|
|
2c45521fb4 | ||
|
|
0870387995 | ||
|
|
be281c78ff | ||
|
|
87c54335db | ||
|
|
12e6c1677b | ||
|
|
f69b789346 | ||
|
|
9f05295f18 | ||
|
|
bf14e4c8c0 | ||
|
|
bd4db09637 | ||
|
|
4b5dcb1c7e | ||
|
|
b7f1ac5e8f | ||
|
|
bcf3c3dc33 | ||
|
|
993c271fa8 | ||
|
|
b8b545a072 | ||
|
|
84b748a361 | ||
|
|
ef2b8cadc7 | ||
|
|
d8cfa2c397 | ||
|
|
d8c7d63f21 | ||
|
|
3d3aa23a65 | ||
|
|
6993fe5df8 | ||
|
|
48d80a8460 | ||
|
|
742c5b3489 | ||
|
|
3cd75c02ee | ||
|
|
854d5f0afe | ||
|
|
daabbfa759 | ||
|
|
42ca4f76bf | ||
|
|
6bf2801b1e | ||
|
|
3be3a28cc9 | ||
|
|
916e1640de | ||
|
|
ecb3b74581 | ||
|
|
2171c7fbe3 | ||
|
|
4a909e5c44 | ||
|
|
c44c239b35 | ||
|
|
6990046a8d | ||
|
|
0750b080f6 | ||
|
|
ca09d2ca66 | ||
|
|
ed3749eaf2 | ||
|
|
fa3c391ee9 | ||
|
|
c6d945b8c0 | ||
|
|
9dc20abbb4 | ||
|
|
94cd543e56 | ||
|
|
4f50939eb2 | ||
|
|
60f792b2ef | ||
|
|
383945b156 | ||
|
|
0e960715ee | ||
|
|
522fd2a9f5 | ||
|
|
9091b3d2a8 | ||
|
|
acc3d2f6d0 | ||
|
|
a9d7ab0193 | ||
|
|
cd720c34dd | ||
|
|
c3839b1e98 | ||
|
|
085eb2a9a0 | ||
|
|
f20c04d523 | ||
|
|
b7b7d6ad3e | ||
|
|
299444d767 | ||
|
|
0dbaa68fc0 | ||
|
|
80fd9e136a | ||
|
|
ba27db5f75 | ||
|
|
4629271fb2 | ||
|
|
91177af98b | ||
|
|
26281d1cbb | ||
|
|
a80c6e12ad | ||
|
|
8e208e8791 | ||
|
|
6e87c2d5cc | ||
|
|
3d96984735 | ||
|
|
4626fdf04a | ||
|
|
a2c013ec43 |
6
.env
6
.env
@@ -6,4 +6,8 @@ MailConfig__UseSSL="false"
|
|||||||
MailConfig__Port=587
|
MailConfig__Port=587
|
||||||
MailConfig__Username=""
|
MailConfig__Username=""
|
||||||
MailConfig__Password=""
|
MailConfig__Password=""
|
||||||
LOGGING__LOGLEVEL__DEFAULT=Error
|
LOGGING__LOGLEVEL__DEFAULT=Error
|
||||||
|
|
||||||
|
# * Uncoment this line if you use postgresSQL as database backend.
|
||||||
|
# * Check the docker-compose.postgresql.yml file
|
||||||
|
#POSTGRES_CONNECTION="Host=postgres;Username=lubelogger;Password=lubepass;Database=lubelogger;"
|
||||||
|
|||||||
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a bug
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Checklist**
|
||||||
|
Please make sure you have performed the following steps before opening a new bug ticket, change `[ ]` to `[x]` to mark it as done
|
||||||
|
|
||||||
|
- [ ] I have read and tried the steps outlined in the [Troubleshooting Guide](https://docs.lubelogger.com/Troubleshooting)
|
||||||
|
- [ ] I have searched through existing issues.
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
<!-- Describe the bug below this line -->
|
||||||
|
|
||||||
|
**Platform**
|
||||||
|
- [ ] Docker Image
|
||||||
|
- [ ] Windows Standalone Executable
|
||||||
|
|
||||||
|
**Browser Console Errors(F12)**
|
||||||
|
<!-- Attach a screenshot or codeblock containing the browser console error -->
|
||||||
|
|
||||||
|
**App/Container Console Error**
|
||||||
|
<!-- Attach a screenshot or codeblock containing the app/container console error -->
|
||||||
|
|
||||||
|
**Screenshots(optional)**
|
||||||
|
<!-- Attach a screenshot describing the bug -->
|
||||||
@@ -27,6 +27,7 @@ namespace CarCareTracker.Controllers
|
|||||||
private readonly IReminderHelper _reminderHelper;
|
private readonly IReminderHelper _reminderHelper;
|
||||||
private readonly IGasHelper _gasHelper;
|
private readonly IGasHelper _gasHelper;
|
||||||
private readonly IUserLogic _userLogic;
|
private readonly IUserLogic _userLogic;
|
||||||
|
private readonly IOdometerLogic _odometerLogic;
|
||||||
private readonly IFileHelper _fileHelper;
|
private readonly IFileHelper _fileHelper;
|
||||||
private readonly IMailHelper _mailHelper;
|
private readonly IMailHelper _mailHelper;
|
||||||
private readonly IConfigHelper _config;
|
private readonly IConfigHelper _config;
|
||||||
@@ -46,7 +47,8 @@ namespace CarCareTracker.Controllers
|
|||||||
IMailHelper mailHelper,
|
IMailHelper mailHelper,
|
||||||
IFileHelper fileHelper,
|
IFileHelper fileHelper,
|
||||||
IConfigHelper config,
|
IConfigHelper config,
|
||||||
IUserLogic userLogic)
|
IUserLogic userLogic,
|
||||||
|
IOdometerLogic odometerLogic)
|
||||||
{
|
{
|
||||||
_dataAccess = dataAccess;
|
_dataAccess = dataAccess;
|
||||||
_noteDataAccess = noteDataAccess;
|
_noteDataAccess = noteDataAccess;
|
||||||
@@ -63,6 +65,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_gasHelper = gasHelper;
|
_gasHelper = gasHelper;
|
||||||
_reminderHelper = reminderHelper;
|
_reminderHelper = reminderHelper;
|
||||||
_userLogic = userLogic;
|
_userLogic = userLogic;
|
||||||
|
_odometerLogic = odometerLogic;
|
||||||
_fileHelper = fileHelper;
|
_fileHelper = fileHelper;
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
@@ -138,8 +141,9 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Mileage = int.Parse(input.Odometer)
|
Mileage = int.Parse(input.Odometer)
|
||||||
};
|
};
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
|
||||||
}
|
}
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Service Record via API - Description: {serviceRecord.Description}");
|
||||||
response.Success = true;
|
response.Success = true;
|
||||||
response.Message = "Service Record Added";
|
response.Message = "Service Record Added";
|
||||||
return Json(response);
|
return Json(response);
|
||||||
@@ -205,8 +209,9 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Mileage = int.Parse(input.Odometer)
|
Mileage = int.Parse(input.Odometer)
|
||||||
};
|
};
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
|
||||||
}
|
}
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Repair Record via API - Description: {repairRecord.Description}");
|
||||||
response.Success = true;
|
response.Success = true;
|
||||||
response.Message = "Repair Record Added";
|
response.Message = "Repair Record Added";
|
||||||
return Json(response);
|
return Json(response);
|
||||||
@@ -272,8 +277,9 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Mileage = int.Parse(input.Odometer)
|
Mileage = int.Parse(input.Odometer)
|
||||||
};
|
};
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
|
||||||
}
|
}
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Upgrade Record via API - Description: {upgradeRecord.Description}");
|
||||||
response.Success = true;
|
response.Success = true;
|
||||||
response.Message = "Upgrade Record Added";
|
response.Message = "Upgrade Record Added";
|
||||||
return Json(response);
|
return Json(response);
|
||||||
@@ -327,6 +333,7 @@ namespace CarCareTracker.Controllers
|
|||||||
Cost = decimal.Parse(input.Cost)
|
Cost = decimal.Parse(input.Cost)
|
||||||
};
|
};
|
||||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
|
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
|
||||||
response.Success = true;
|
response.Success = true;
|
||||||
response.Message = "Tax Record Added";
|
response.Message = "Tax Record Added";
|
||||||
return Json(response);
|
return Json(response);
|
||||||
@@ -341,11 +348,24 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/odometerrecords/latest")]
|
||||||
|
public IActionResult LastOdometer(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = GetMaxMileage(vehicleId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
[Route("/api/vehicle/odometerrecords")]
|
[Route("/api/vehicle/odometerrecords")]
|
||||||
public IActionResult OdometerRecords(int vehicleId)
|
public IActionResult OdometerRecords(int vehicleId)
|
||||||
{
|
{
|
||||||
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), Odometer = x.Mileage.ToString(), Notes = x.Notes });
|
//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 });
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
@@ -376,9 +396,11 @@ namespace CarCareTracker.Controllers
|
|||||||
VehicleId = vehicleId,
|
VehicleId = vehicleId,
|
||||||
Date = DateTime.Parse(input.Date),
|
Date = DateTime.Parse(input.Date),
|
||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
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)
|
||||||
};
|
};
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
|
||||||
response.Success = true;
|
response.Success = true;
|
||||||
response.Message = "Odometer Record Added";
|
response.Message = "Odometer Record Added";
|
||||||
return Json(response);
|
return Json(response);
|
||||||
@@ -458,8 +480,9 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Mileage = int.Parse(input.Odometer)
|
Mileage = int.Parse(input.Odometer)
|
||||||
};
|
};
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
|
||||||
}
|
}
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Gas record via API - Mileage: {gasRecord.Mileage.ToString()}");
|
||||||
response.Success = true;
|
response.Success = true;
|
||||||
response.Message = "Gas Record Added";
|
response.Message = "Gas Record Added";
|
||||||
return Json(response);
|
return Json(response);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace CarCareTracker.Controllers
|
|||||||
private readonly ILogger<HomeController> _logger;
|
private readonly ILogger<HomeController> _logger;
|
||||||
private readonly IVehicleDataAccess _dataAccess;
|
private readonly IVehicleDataAccess _dataAccess;
|
||||||
private readonly IUserLogic _userLogic;
|
private readonly IUserLogic _userLogic;
|
||||||
|
private readonly ILoginLogic _loginLogic;
|
||||||
private readonly IFileHelper _fileHelper;
|
private readonly IFileHelper _fileHelper;
|
||||||
private readonly IConfigHelper _config;
|
private readonly IConfigHelper _config;
|
||||||
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
||||||
@@ -23,6 +24,7 @@ namespace CarCareTracker.Controllers
|
|||||||
public HomeController(ILogger<HomeController> logger,
|
public HomeController(ILogger<HomeController> logger,
|
||||||
IVehicleDataAccess dataAccess,
|
IVehicleDataAccess dataAccess,
|
||||||
IUserLogic userLogic,
|
IUserLogic userLogic,
|
||||||
|
ILoginLogic loginLogic,
|
||||||
IConfigHelper configuration,
|
IConfigHelper configuration,
|
||||||
IFileHelper fileHelper,
|
IFileHelper fileHelper,
|
||||||
IExtraFieldDataAccess extraFieldDataAccess,
|
IExtraFieldDataAccess extraFieldDataAccess,
|
||||||
@@ -37,6 +39,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_extraFieldDataAccess = extraFieldDataAccess;
|
_extraFieldDataAccess = extraFieldDataAccess;
|
||||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||||
_reminderHelper = reminderHelper;
|
_reminderHelper = reminderHelper;
|
||||||
|
_loginLogic = loginLogic;
|
||||||
}
|
}
|
||||||
private int GetUserID()
|
private int GetUserID()
|
||||||
{
|
{
|
||||||
@@ -63,15 +66,14 @@ namespace CarCareTracker.Controllers
|
|||||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||||
}
|
}
|
||||||
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
|
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
|
||||||
foreach(Vehicle vehicle in vehiclesStored)
|
foreach (Vehicle vehicle in vehiclesStored)
|
||||||
{
|
{
|
||||||
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
||||||
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
|
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
|
||||||
//we don't care about mileages so we can basically fake the current vehicle mileage.
|
//we don't care about mileages so we can basically fake the current vehicle mileage.
|
||||||
if (vehicleReminders.Any())
|
if (vehicleReminders.Any())
|
||||||
{
|
{
|
||||||
var maxMileage = vehicleReminders.Max(x => x.Mileage) + 1000;
|
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, 0, DateTime.Now);
|
||||||
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, maxMileage, DateTime.Now);
|
|
||||||
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Date = x.Date, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate} - {x.Description}" }).ToList();
|
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Date = x.Date, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate} - {x.Description}" }).ToList();
|
||||||
reminders.AddRange(reminderUrgency);
|
reminders.AddRange(reminderUrgency);
|
||||||
}
|
}
|
||||||
@@ -81,7 +83,7 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult ViewCalendarReminder(int reminderId)
|
public IActionResult ViewCalendarReminder(int reminderId)
|
||||||
{
|
{
|
||||||
var reminder = _reminderRecordDataAccess.GetReminderRecordById(reminderId);
|
var reminder = _reminderRecordDataAccess.GetReminderRecordById(reminderId);
|
||||||
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(new List<ReminderRecord> { reminder }, reminder.Mileage + 1000, DateTime.Now).FirstOrDefault();
|
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(new List<ReminderRecord> { reminder }, 0, DateTime.Now).FirstOrDefault();
|
||||||
return PartialView("_ReminderRecordCalendarModal", reminderUrgency);
|
return PartialView("_ReminderRecordCalendarModal", reminderUrgency);
|
||||||
}
|
}
|
||||||
public IActionResult Settings()
|
public IActionResult Settings()
|
||||||
@@ -98,9 +100,23 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult WriteToSettings(UserConfig userConfig)
|
public IActionResult WriteToSettings(UserConfig userConfig)
|
||||||
{
|
{
|
||||||
|
//retrieve existing userConfig.
|
||||||
|
var existingConfig = _config.GetUserConfig(User);
|
||||||
|
//copy over stuff that persists
|
||||||
|
userConfig.UserColumnPreferences = existingConfig.UserColumnPreferences;
|
||||||
|
userConfig.ReminderUrgencyConfig = existingConfig.ReminderUrgencyConfig;
|
||||||
var result = _config.SaveUserConfig(User, userConfig);
|
var result = _config.SaveUserConfig(User, userConfig);
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveReminderUrgencyThreshold(ReminderUrgencyConfig reminderUrgencyConfig)
|
||||||
|
{
|
||||||
|
//retrieve existing userConfig.
|
||||||
|
var existingConfig = _config.GetUserConfig(User);
|
||||||
|
existingConfig.ReminderUrgencyConfig = reminderUrgencyConfig;
|
||||||
|
var result = _config.SaveUserConfig(User, existingConfig);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
public IActionResult Privacy()
|
public IActionResult Privacy()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
@@ -129,6 +145,51 @@ namespace CarCareTracker.Controllers
|
|||||||
var recordExtraFields = _extraFieldDataAccess.GetExtraFieldsById(record.Id);
|
var recordExtraFields = _extraFieldDataAccess.GetExtraFieldsById(record.Id);
|
||||||
return PartialView("_ExtraFields", recordExtraFields);
|
return PartialView("_ExtraFields", recordExtraFields);
|
||||||
}
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GenerateTokenForUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//get current user email address.
|
||||||
|
var emailAddress = User.FindFirstValue(ClaimTypes.Email);
|
||||||
|
if (!string.IsNullOrWhiteSpace(emailAddress))
|
||||||
|
{
|
||||||
|
var result = _loginLogic.GenerateTokenForEmailAddress(emailAddress, false);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
return Json(false);
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult UpdateUserAccount(LoginModel userAccount)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetUserID();
|
||||||
|
if (userId > 0)
|
||||||
|
{
|
||||||
|
var result = _loginLogic.UpdateUserDetails(userId, userAccount);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetUserAccountInformationModal()
|
||||||
|
{
|
||||||
|
var emailAddress = User.FindFirstValue(ClaimTypes.Email);
|
||||||
|
var userName = User.Identity.Name;
|
||||||
|
return PartialView("_AccountModal", new UserData() { EmailAddress = emailAddress, UserName = userName });
|
||||||
|
}
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
public IActionResult Error()
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,10 +45,17 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
public IActionResult GetRemoteLoginLink()
|
public IActionResult GetRemoteLoginLink()
|
||||||
{
|
{
|
||||||
var remoteAuthURL = _config.GetOpenIDConfig().RemoteAuthURL;
|
var remoteAuthConfig = _config.GetOpenIDConfig();
|
||||||
|
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
|
||||||
|
remoteAuthConfig.State = generatedState;
|
||||||
|
if (remoteAuthConfig.ValidateState)
|
||||||
|
{
|
||||||
|
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
|
||||||
|
}
|
||||||
|
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
|
||||||
return Json(remoteAuthURL);
|
return Json(remoteAuthURL);
|
||||||
}
|
}
|
||||||
public async Task<IActionResult> RemoteAuth(string code)
|
public async Task<IActionResult> RemoteAuth(string code, string state = "")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -58,6 +65,20 @@ namespace CarCareTracker.Controllers
|
|||||||
//create http client to retrieve user token from OIDC
|
//create http client to retrieve user token from OIDC
|
||||||
var httpClient = new HttpClient();
|
var httpClient = new HttpClient();
|
||||||
var openIdConfig = _config.GetOpenIDConfig();
|
var openIdConfig = _config.GetOpenIDConfig();
|
||||||
|
//check if validate state is enabled.
|
||||||
|
if (openIdConfig.ValidateState)
|
||||||
|
{
|
||||||
|
var storedStateValue = Request.Cookies["OIDC_STATE"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(storedStateValue))
|
||||||
|
{
|
||||||
|
Response.Cookies.Delete("OIDC_STATE");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(storedStateValue) || string.IsNullOrWhiteSpace(state) || storedStateValue != state)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Failed OIDC State Validation - Try disabling state validation if you are confident this is not a malicious attempt.");
|
||||||
|
return new RedirectResult("/Login");
|
||||||
|
}
|
||||||
|
}
|
||||||
var httpParams = new List<KeyValuePair<string, string>>
|
var httpParams = new List<KeyValuePair<string, string>>
|
||||||
{
|
{
|
||||||
new KeyValuePair<string, string>("code", code),
|
new KeyValuePair<string, string>("code", code),
|
||||||
@@ -97,8 +118,17 @@ namespace CarCareTracker.Controllers
|
|||||||
_logger.LogInformation($"User {userEmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
_logger.LogInformation($"User {userEmailAddress} tried to login via OpenID but is not a registered user in LubeLogger.");
|
||||||
return View("OpenIDRegistration", model: userEmailAddress);
|
return View("OpenIDRegistration", model: userEmailAddress);
|
||||||
}
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("OpenID Provider did not provide a valid email address for the user");
|
||||||
}
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("OpenID Provider did not provide a valid id_token");
|
||||||
}
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("OpenID Provider did not provide a code.");
|
||||||
}
|
}
|
||||||
} catch (Exception ex)
|
} catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ namespace CarCareTracker.Controllers
|
|||||||
private readonly IReminderHelper _reminderHelper;
|
private readonly IReminderHelper _reminderHelper;
|
||||||
private readonly IReportHelper _reportHelper;
|
private readonly IReportHelper _reportHelper;
|
||||||
private readonly IUserLogic _userLogic;
|
private readonly IUserLogic _userLogic;
|
||||||
|
private readonly IOdometerLogic _odometerLogic;
|
||||||
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
||||||
|
|
||||||
public VehicleController(ILogger<VehicleController> logger,
|
public VehicleController(ILogger<VehicleController> logger,
|
||||||
@@ -57,6 +58,7 @@ namespace CarCareTracker.Controllers
|
|||||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||||
IExtraFieldDataAccess extraFieldDataAccess,
|
IExtraFieldDataAccess extraFieldDataAccess,
|
||||||
IUserLogic userLogic,
|
IUserLogic userLogic,
|
||||||
|
IOdometerLogic odometerLogic,
|
||||||
IWebHostEnvironment webEnv,
|
IWebHostEnvironment webEnv,
|
||||||
IConfigHelper config)
|
IConfigHelper config)
|
||||||
{
|
{
|
||||||
@@ -79,6 +81,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||||
_extraFieldDataAccess = extraFieldDataAccess;
|
_extraFieldDataAccess = extraFieldDataAccess;
|
||||||
_userLogic = userLogic;
|
_userLogic = userLogic;
|
||||||
|
_odometerLogic = odometerLogic;
|
||||||
_webEnv = webEnv;
|
_webEnv = webEnv;
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
@@ -97,13 +100,14 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult AddVehiclePartialView()
|
public IActionResult AddVehiclePartialView()
|
||||||
{
|
{
|
||||||
return PartialView("_VehicleModal", new Vehicle());
|
return PartialView("_VehicleModal", new Vehicle() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.VehicleRecord).ExtraFields });
|
||||||
}
|
}
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetEditVehiclePartialViewById(int vehicleId)
|
public IActionResult GetEditVehiclePartialViewById(int vehicleId)
|
||||||
{
|
{
|
||||||
var data = _dataAccess.GetVehicleById(vehicleId);
|
var data = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
data.ExtraFields = StaticHelper.AddExtraFields(data.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.VehicleRecord).ExtraFields);
|
||||||
return PartialView("_VehicleModal", data);
|
return PartialView("_VehicleModal", data);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -126,6 +130,10 @@ namespace CarCareTracker.Controllers
|
|||||||
if (isNewAddition)
|
if (isNewAddition)
|
||||||
{
|
{
|
||||||
_userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id);
|
_userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id);
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Added Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Edited Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}");
|
||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
@@ -150,8 +158,13 @@ namespace CarCareTracker.Controllers
|
|||||||
_planRecordDataAccess.DeleteAllPlanRecordsByVehicleId(vehicleId) &&
|
_planRecordDataAccess.DeleteAllPlanRecordsByVehicleId(vehicleId) &&
|
||||||
_planRecordTemplateDataAccess.DeleteAllPlanRecordTemplatesByVehicleId(vehicleId) &&
|
_planRecordTemplateDataAccess.DeleteAllPlanRecordTemplatesByVehicleId(vehicleId) &&
|
||||||
_supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) &&
|
_supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) &&
|
||||||
|
_odometerRecordDataAccess.DeleteAllOdometerRecordsByVehicleId(vehicleId) &&
|
||||||
_userLogic.DeleteAllAccessToVehicle(vehicleId) &&
|
_userLogic.DeleteAllAccessToVehicle(vehicleId) &&
|
||||||
_dataAccess.DeleteVehicle(vehicleId);
|
_dataAccess.DeleteVehicle(vehicleId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, "Deleted Vehicle");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -264,7 +277,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
if (vehicleRecords.Any())
|
if (vehicleRecords.Any())
|
||||||
{
|
{
|
||||||
var exportData = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), Notes = x.Notes, 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) });
|
||||||
using (var writer = new StreamWriter(fullExportFilePath))
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
{
|
{
|
||||||
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
@@ -459,7 +472,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_gasRecordDataAccess.SaveGasRecordToVehicle(convertedRecord);
|
_gasRecordDataAccess.SaveGasRecordToVehicle(convertedRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = convertedRecord.Date,
|
Date = convertedRecord.Date,
|
||||||
VehicleId = convertedRecord.VehicleId,
|
VehicleId = convertedRecord.VehicleId,
|
||||||
@@ -484,7 +497,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
|
_serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = convertedRecord.Date,
|
Date = convertedRecord.Date,
|
||||||
VehicleId = convertedRecord.VehicleId,
|
VehicleId = convertedRecord.VehicleId,
|
||||||
@@ -499,6 +512,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
VehicleId = vehicleId,
|
VehicleId = vehicleId,
|
||||||
Date = DateTime.Parse(importModel.Date),
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
InitialMileage = string.IsNullOrWhiteSpace(importModel.InitialOdometer) ? 0 : decimal.ToInt32(decimal.Parse(importModel.InitialOdometer, NumberStyles.Any)),
|
||||||
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
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()
|
||||||
@@ -539,7 +553,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
|
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = convertedRecord.Date,
|
Date = convertedRecord.Date,
|
||||||
VehicleId = convertedRecord.VehicleId,
|
VehicleId = convertedRecord.VehicleId,
|
||||||
@@ -563,7 +577,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
|
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = convertedRecord.Date,
|
Date = convertedRecord.Date,
|
||||||
VehicleId = convertedRecord.VehicleId,
|
VehicleId = convertedRecord.VehicleId,
|
||||||
@@ -645,7 +659,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = DateTime.Parse(gasRecord.Date),
|
Date = DateTime.Parse(gasRecord.Date),
|
||||||
VehicleId = gasRecord.VehicleId,
|
VehicleId = gasRecord.VehicleId,
|
||||||
@@ -655,6 +669,10 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
gasRecord.Files = gasRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
gasRecord.Files = gasRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
|
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), gasRecord.VehicleId, User.Identity.Name, $"{(gasRecord.Id == default ? "Created" : "Edited")} Gas Record - Mileage: {gasRecord.Mileage.ToString()}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -699,6 +717,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteGasRecordById(int gasRecordId)
|
public IActionResult DeleteGasRecordById(int gasRecordId)
|
||||||
{
|
{
|
||||||
var result = _gasRecordDataAccess.DeleteGasRecordById(gasRecordId);
|
var result = _gasRecordDataAccess.DeleteGasRecordById(gasRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -710,6 +732,61 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _config.SaveUserConfig(User, currentConfig);
|
var result = _config.SaveUserConfig(User, currentConfig);
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetGasRecordsEditModal(List<int> recordIds)
|
||||||
|
{
|
||||||
|
return PartialView("_GasRecordsModal", new GasRecordEditModel { RecordIds = recordIds });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveMultipleGasRecords(GasRecordEditModel editModel)
|
||||||
|
{
|
||||||
|
var dateIsEdited = editModel.EditRecord.Date != default;
|
||||||
|
var mileageIsEdited = editModel.EditRecord.Mileage != default;
|
||||||
|
var consumptionIsEdited = editModel.EditRecord.Gallons != default;
|
||||||
|
var costIsEdited = editModel.EditRecord.Cost != default;
|
||||||
|
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
|
||||||
|
var tagsIsEdited = editModel.EditRecord.Tags.Any();
|
||||||
|
//handle clear overrides
|
||||||
|
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Tags = new List<string>();
|
||||||
|
}
|
||||||
|
if (noteIsEdited && editModel.EditRecord.Notes == "---")
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Notes = "";
|
||||||
|
}
|
||||||
|
bool result = false;
|
||||||
|
foreach (int recordId in editModel.RecordIds)
|
||||||
|
{
|
||||||
|
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||||
|
if (dateIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Date = editModel.EditRecord.Date;
|
||||||
|
}
|
||||||
|
if (consumptionIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Gallons = editModel.EditRecord.Gallons;
|
||||||
|
}
|
||||||
|
if (costIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Cost = editModel.EditRecord.Cost;
|
||||||
|
}
|
||||||
|
if (mileageIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Mileage = editModel.EditRecord.Mileage;
|
||||||
|
}
|
||||||
|
if (noteIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Notes = editModel.EditRecord.Notes;
|
||||||
|
}
|
||||||
|
if (tagsIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Tags = editModel.EditRecord.Tags;
|
||||||
|
}
|
||||||
|
result = _gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region "Service Records"
|
#region "Service Records"
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
@@ -733,7 +810,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = DateTime.Parse(serviceRecord.Date),
|
Date = DateTime.Parse(serviceRecord.Date),
|
||||||
VehicleId = serviceRecord.VehicleId,
|
VehicleId = serviceRecord.VehicleId,
|
||||||
@@ -753,6 +830,10 @@ namespace CarCareTracker.Controllers
|
|||||||
PushbackRecurringReminderRecordWithChecks(serviceRecord.ReminderRecordId);
|
PushbackRecurringReminderRecordWithChecks(serviceRecord.ReminderRecordId);
|
||||||
}
|
}
|
||||||
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
|
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), serviceRecord.VehicleId, User.Identity.Name, $"{(serviceRecord.Id == default ? "Created" : "Edited")} Service Record - Description: {serviceRecord.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -785,6 +866,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteServiceRecordById(int serviceRecordId)
|
public IActionResult DeleteServiceRecordById(int serviceRecordId)
|
||||||
{
|
{
|
||||||
var result = _serviceRecordDataAccess.DeleteServiceRecordById(serviceRecordId);
|
var result = _serviceRecordDataAccess.DeleteServiceRecordById(serviceRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -810,7 +895,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = DateTime.Parse(collisionRecord.Date),
|
Date = DateTime.Parse(collisionRecord.Date),
|
||||||
VehicleId = collisionRecord.VehicleId,
|
VehicleId = collisionRecord.VehicleId,
|
||||||
@@ -830,6 +915,10 @@ namespace CarCareTracker.Controllers
|
|||||||
PushbackRecurringReminderRecordWithChecks(collisionRecord.ReminderRecordId);
|
PushbackRecurringReminderRecordWithChecks(collisionRecord.ReminderRecordId);
|
||||||
}
|
}
|
||||||
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
|
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), collisionRecord.VehicleId, User.Identity.Name, $"{(collisionRecord.Id == default ? "Created" : "Edited")} Repair Record - Description: {collisionRecord.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -862,6 +951,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
|
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
|
||||||
{
|
{
|
||||||
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(collisionRecordId);
|
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(collisionRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -933,6 +1026,10 @@ namespace CarCareTracker.Controllers
|
|||||||
PushbackRecurringReminderRecordWithChecks(taxRecord.ReminderRecordId);
|
PushbackRecurringReminderRecordWithChecks(taxRecord.ReminderRecordId);
|
||||||
}
|
}
|
||||||
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
|
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), taxRecord.VehicleId, User.Identity.Name, $"{(taxRecord.Id == default ? "Created" : "Edited")} Tax Record - Description: {taxRecord.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -966,6 +1063,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteTaxRecordById(int taxRecordId)
|
public IActionResult DeleteTaxRecordById(int taxRecordId)
|
||||||
{
|
{
|
||||||
var result = _taxRecordDataAccess.DeleteTaxRecordById(taxRecordId);
|
var result = _taxRecordDataAccess.DeleteTaxRecordById(taxRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -1003,7 +1104,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
MonthName = x.Key.MonthName,
|
MonthName = x.Key.MonthName,
|
||||||
Cost = x.Sum(y => y.Cost),
|
Cost = x.Sum(y => y.Cost),
|
||||||
DistanceTraveled = x.Any(y => y.MinMileage != default) ? x.Max(y => y.MaxMileage) - x.Where(y => y.MinMileage != default).Min(y => y.MinMileage) : 0
|
DistanceTraveled = x.Max(y => y.DistanceTraveled)
|
||||||
}).ToList();
|
}).ToList();
|
||||||
//get reminders
|
//get reminders
|
||||||
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now);
|
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now);
|
||||||
@@ -1131,6 +1232,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var records = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
var records = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
|
DataType = ImportMode.ServiceRecord,
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
Files = x.Files
|
Files = x.Files
|
||||||
@@ -1141,6 +1243,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var records = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
var records = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
|
DataType = ImportMode.RepairRecord,
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
Files = x.Files
|
Files = x.Files
|
||||||
@@ -1151,6 +1254,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var records = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
var records = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
|
DataType = ImportMode.UpgradeRecord,
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
Files = x.Files
|
Files = x.Files
|
||||||
@@ -1161,6 +1265,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var records = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
var records = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
|
DataType = ImportMode.GasRecord,
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
Files = x.Files
|
Files = x.Files
|
||||||
@@ -1171,6 +1276,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var records = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
var records = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
|
DataType = ImportMode.TaxRecord,
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = 0,
|
Odometer = 0,
|
||||||
Files = x.Files
|
Files = x.Files
|
||||||
@@ -1181,6 +1287,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var records = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
var records = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
{
|
{
|
||||||
|
DataType = ImportMode.OdometerRecord,
|
||||||
Date = x.Date,
|
Date = x.Date,
|
||||||
Odometer = x.Mileage,
|
Odometer = x.Mileage,
|
||||||
Files = x.Files
|
Files = x.Files
|
||||||
@@ -1206,7 +1313,27 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
var vehicleHistory = new VehicleHistoryViewModel();
|
var vehicleHistory = new VehicleHistoryViewModel();
|
||||||
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
|
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
vehicleHistory.Odometer = GetMaxMileage(vehicleId).ToString("N0");
|
var maxMileage = GetMaxMileage(vehicleId);
|
||||||
|
vehicleHistory.Odometer = maxMileage.ToString("N0");
|
||||||
|
var minMileage = GetMinMileage(vehicleId);
|
||||||
|
var distanceTraveled = maxMileage - minMileage;
|
||||||
|
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
|
||||||
|
{
|
||||||
|
var endDate = vehicleHistory.VehicleData.SoldDate;
|
||||||
|
if (string.IsNullOrWhiteSpace(endDate))
|
||||||
|
{
|
||||||
|
endDate = DateTime.Now.ToShortDateString();
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
vehicleHistory.DaysOwned = (DateTime.Parse(endDate) - DateTime.Parse(vehicleHistory.VehicleData.PurchaseDate)).Days.ToString("N0");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
vehicleHistory.DaysOwned = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
List<GenericReportModel> reportData = new List<GenericReportModel>();
|
List<GenericReportModel> reportData = new List<GenericReportModel>();
|
||||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
@@ -1216,8 +1343,15 @@ namespace CarCareTracker.Controllers
|
|||||||
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||||
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
||||||
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||||
|
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
|
||||||
vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost);
|
vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost);
|
||||||
vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost);
|
vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost);
|
||||||
|
if (distanceTraveled != default)
|
||||||
|
{
|
||||||
|
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0");
|
||||||
|
vehicleHistory.TotalCostPerMile = vehicleHistory.TotalCost / distanceTraveled;
|
||||||
|
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
|
||||||
|
}
|
||||||
var averageMPG = "0";
|
var averageMPG = "0";
|
||||||
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
|
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
|
||||||
if (gasViewModels.Any())
|
if (gasViewModels.Any())
|
||||||
@@ -1343,7 +1477,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
MonthName = x.Key.MonthName,
|
MonthName = x.Key.MonthName,
|
||||||
Cost = x.Sum(y => y.Cost),
|
Cost = x.Sum(y => y.Cost),
|
||||||
DistanceTraveled = x.Any(y => y.MinMileage != default) ? x.Max(y => y.MaxMileage) - x.Where(y => y.MinMileage != default).Min(y => y.MinMileage) : 0
|
DistanceTraveled = x.Max(y => y.DistanceTraveled)
|
||||||
}).ToList();
|
}).ToList();
|
||||||
return PartialView("_GasCostByMonthReport", groupedRecord);
|
return PartialView("_GasCostByMonthReport", groupedRecord);
|
||||||
}
|
}
|
||||||
@@ -1380,6 +1514,37 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return numbersArray.Any() ? numbersArray.Max() : 0;
|
return numbersArray.Any() ? numbersArray.Max() : 0;
|
||||||
}
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
private int GetMinMileage(int vehicleId)
|
||||||
|
{
|
||||||
|
var numbersArray = new List<int>();
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
|
||||||
|
if (serviceRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(serviceRecords.Min(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
|
||||||
|
if (repairRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(repairRecords.Min(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
|
||||||
|
if (gasRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(gasRecords.Min(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
|
||||||
|
if (upgradeRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(upgradeRecords.Min(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
|
||||||
|
if (odometerRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(odometerRecords.Min(x => x.Mileage));
|
||||||
|
}
|
||||||
|
return numbersArray.Any() ? numbersArray.Min() : 0;
|
||||||
|
}
|
||||||
private List<ReminderRecordViewModel> GetRemindersAndUrgency(int vehicleId, DateTime dateCompare)
|
private List<ReminderRecordViewModel> GetRemindersAndUrgency(int vehicleId, DateTime dateCompare)
|
||||||
{
|
{
|
||||||
var currentMileage = GetMaxMileage(vehicleId);
|
var currentMileage = GetMaxMileage(vehicleId);
|
||||||
@@ -1474,6 +1639,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
|
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
|
||||||
{
|
{
|
||||||
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
|
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), reminderRecord.VehicleId, User.Identity.Name, $"{(reminderRecord.Id == default ? "Created" : "Edited")} Reminder - Description: {reminderRecord.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -1514,6 +1683,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteReminderRecordById(int reminderRecordId)
|
public IActionResult DeleteReminderRecordById(int reminderRecordId)
|
||||||
{
|
{
|
||||||
var result = _reminderRecordDataAccess.DeleteReminderRecordById(reminderRecordId);
|
var result = _reminderRecordDataAccess.DeleteReminderRecordById(reminderRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -1539,7 +1712,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = DateTime.Parse(upgradeRecord.Date),
|
Date = DateTime.Parse(upgradeRecord.Date),
|
||||||
VehicleId = upgradeRecord.VehicleId,
|
VehicleId = upgradeRecord.VehicleId,
|
||||||
@@ -1559,6 +1732,10 @@ namespace CarCareTracker.Controllers
|
|||||||
PushbackRecurringReminderRecordWithChecks(upgradeRecord.ReminderRecordId);
|
PushbackRecurringReminderRecordWithChecks(upgradeRecord.ReminderRecordId);
|
||||||
}
|
}
|
||||||
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
|
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), upgradeRecord.VehicleId, User.Identity.Name, $"{(upgradeRecord.Id == default ? "Created" : "Edited")} Upgrade Record - Description: {upgradeRecord.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -1591,6 +1768,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
|
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
|
||||||
{
|
{
|
||||||
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(upgradeRecordId);
|
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(upgradeRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -1615,6 +1796,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult SaveNoteToVehicleId(Note note)
|
public IActionResult SaveNoteToVehicleId(Note note)
|
||||||
{
|
{
|
||||||
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), note.VehicleId, User.Identity.Name, $"{(note.Id == default ? "Created" : "Edited")} Note - Description: {note.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -1632,19 +1817,24 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteNoteById(int noteId)
|
public IActionResult DeleteNoteById(int noteId)
|
||||||
{
|
{
|
||||||
var result = _noteDataAccess.DeleteNoteById(noteId);
|
var result = _noteDataAccess.DeleteNoteById(noteId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult PinNotes(List<int> noteIds, bool isToggle = false, bool pinStatus = false)
|
public IActionResult PinNotes(List<int> noteIds, bool isToggle = false, bool pinStatus = false)
|
||||||
{
|
{
|
||||||
var result = false;
|
var result = false;
|
||||||
foreach(int noteId in noteIds)
|
foreach (int noteId in noteIds)
|
||||||
{
|
{
|
||||||
var existingNote = _noteDataAccess.GetNoteById(noteId);
|
var existingNote = _noteDataAccess.GetNoteById(noteId);
|
||||||
if (isToggle)
|
if (isToggle)
|
||||||
{
|
{
|
||||||
existingNote.Pinned = !existingNote.Pinned;
|
existingNote.Pinned = !existingNote.Pinned;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
existingNote.Pinned = pinStatus;
|
existingNote.Pinned = pinStatus;
|
||||||
}
|
}
|
||||||
@@ -1752,6 +1942,10 @@ namespace CarCareTracker.Controllers
|
|||||||
//move files from temp.
|
//move files from temp.
|
||||||
supplyRecord.Files = supplyRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
supplyRecord.Files = supplyRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
|
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), supplyRecord.VehicleId, User.Identity.Name, $"{(supplyRecord.Id == default ? "Created" : "Edited")} Supply Record - Description: {supplyRecord.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -1786,6 +1980,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
|
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
|
||||||
{
|
{
|
||||||
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(supplyRecordId);
|
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(supplyRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -1813,6 +2011,10 @@ namespace CarCareTracker.Controllers
|
|||||||
planRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description);
|
planRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description);
|
||||||
}
|
}
|
||||||
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
|
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), planRecord.VehicleId, User.Identity.Name, $"{(planRecord.Id == default ? "Created" : "Edited")} Plan Record - Description: {planRecord.Description}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -1904,7 +2106,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
{
|
{
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(new OdometerRecord
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
{
|
{
|
||||||
Date = DateTime.Now,
|
Date = DateTime.Now,
|
||||||
VehicleId = existingRecord.VehicleId,
|
VehicleId = existingRecord.VehicleId,
|
||||||
@@ -1998,15 +2200,32 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeletePlanRecordById(int planRecordId)
|
public IActionResult DeletePlanRecordById(int planRecordId)
|
||||||
{
|
{
|
||||||
var result = _planRecordDataAccess.DeletePlanRecordById(planRecordId);
|
var result = _planRecordDataAccess.DeletePlanRecordById(planRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region "Odometer Records"
|
#region "Odometer Records"
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult ForceRecalculateDistanceByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
result = _odometerLogic.AutoConvertOdometerRecord(result);
|
||||||
|
return Json(result.Any());
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetOdometerRecordsByVehicleId(int vehicleId)
|
public IActionResult GetOdometerRecordsByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
//determine if conversion is needed.
|
||||||
|
if (result.All(x => x.InitialMileage == default))
|
||||||
|
{
|
||||||
|
result = _odometerLogic.AutoConvertOdometerRecord(result);
|
||||||
|
}
|
||||||
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
if (_useDescending)
|
if (_useDescending)
|
||||||
{
|
{
|
||||||
@@ -2024,12 +2243,66 @@ namespace CarCareTracker.Controllers
|
|||||||
//move files from temp.
|
//move files from temp.
|
||||||
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
|
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), odometerRecord.VehicleId, User.Identity.Name, $"{(odometerRecord.Id == default ? "Created" : "Edited")} Odometer Record - Mileage: {odometerRecord.Mileage.ToString()}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetAddOdometerRecordPartialView()
|
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
|
||||||
{
|
{
|
||||||
return PartialView("_OdometerRecordModal", new OdometerRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields });
|
return PartialView("_OdometerRecordModal", new OdometerRecordInput() { InitialMileage = _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()), ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetOdometerRecordsEditModal(List<int> recordIds)
|
||||||
|
{
|
||||||
|
return PartialView("_OdometerRecordsModal", new OdometerRecordEditModel { RecordIds = recordIds });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveMultipleOdometerRecords(OdometerRecordEditModel editModel)
|
||||||
|
{
|
||||||
|
var dateIsEdited = editModel.EditRecord.Date != default;
|
||||||
|
var initialMileageIsEdited = editModel.EditRecord.InitialMileage != default;
|
||||||
|
var mileageIsEdited = editModel.EditRecord.Mileage != default;
|
||||||
|
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
|
||||||
|
var tagsIsEdited = editModel.EditRecord.Tags.Any();
|
||||||
|
//handle clear overrides
|
||||||
|
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Tags = new List<string>();
|
||||||
|
}
|
||||||
|
if (noteIsEdited && editModel.EditRecord.Notes == "---")
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Notes = "";
|
||||||
|
}
|
||||||
|
bool result = false;
|
||||||
|
foreach (int recordId in editModel.RecordIds)
|
||||||
|
{
|
||||||
|
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
|
||||||
|
if (dateIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Date = editModel.EditRecord.Date;
|
||||||
|
}
|
||||||
|
if (initialMileageIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.InitialMileage = editModel.EditRecord.InitialMileage;
|
||||||
|
}
|
||||||
|
if (mileageIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Mileage = editModel.EditRecord.Mileage;
|
||||||
|
}
|
||||||
|
if (noteIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Notes = editModel.EditRecord.Notes;
|
||||||
|
}
|
||||||
|
if (tagsIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Tags = editModel.EditRecord.Tags;
|
||||||
|
}
|
||||||
|
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
|
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
|
||||||
@@ -2040,6 +2313,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
Id = result.Id,
|
Id = result.Id,
|
||||||
Date = result.Date.ToShortDateString(),
|
Date = result.Date.ToShortDateString(),
|
||||||
|
InitialMileage = result.InitialMileage,
|
||||||
Mileage = result.Mileage,
|
Mileage = result.Mileage,
|
||||||
Notes = result.Notes,
|
Notes = result.Notes,
|
||||||
VehicleId = result.VehicleId,
|
VehicleId = result.VehicleId,
|
||||||
@@ -2053,6 +2327,10 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
|
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
|
||||||
{
|
{
|
||||||
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(odometerRecordId);
|
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(odometerRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@@ -2154,6 +2432,10 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Moved multiple {source.ToString()} to {destination.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
public IActionResult DeleteRecords(List<int> recordIds, ImportMode importMode)
|
public IActionResult DeleteRecords(List<int> recordIds, ImportMode importMode)
|
||||||
@@ -2192,6 +2474,69 @@ namespace CarCareTracker.Controllers
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult AdjustRecordsOdometer(List<int> recordIds, int vehicleId, ImportMode importMode)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
//get vehicle's odometer adjustments
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
foreach (int recordId in recordIds)
|
||||||
|
{
|
||||||
|
switch (importMode)
|
||||||
|
{
|
||||||
|
case ImportMode.ServiceRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
|
||||||
|
existingRecord.Mileage += int.Parse(vehicleData.OdometerDifference);
|
||||||
|
existingRecord.Mileage = decimal.ToInt32(existingRecord.Mileage * decimal.Parse(vehicleData.OdometerMultiplier, NumberStyles.Any));
|
||||||
|
result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.RepairRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
|
||||||
|
existingRecord.Mileage += int.Parse(vehicleData.OdometerDifference);
|
||||||
|
existingRecord.Mileage = decimal.ToInt32(existingRecord.Mileage * decimal.Parse(vehicleData.OdometerMultiplier, NumberStyles.Any));
|
||||||
|
result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.UpgradeRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
|
||||||
|
existingRecord.Mileage += int.Parse(vehicleData.OdometerDifference);
|
||||||
|
existingRecord.Mileage = decimal.ToInt32(existingRecord.Mileage * decimal.Parse(vehicleData.OdometerMultiplier, NumberStyles.Any));
|
||||||
|
result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.GasRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||||
|
existingRecord.Mileage += int.Parse(vehicleData.OdometerDifference);
|
||||||
|
existingRecord.Mileage = decimal.ToInt32(existingRecord.Mileage * decimal.Parse(vehicleData.OdometerMultiplier, NumberStyles.Any));
|
||||||
|
result = _gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImportMode.OdometerRecord:
|
||||||
|
{
|
||||||
|
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
|
||||||
|
existingRecord.Mileage += int.Parse(vehicleData.OdometerDifference);
|
||||||
|
existingRecord.Mileage = decimal.ToInt32(existingRecord.Mileage * decimal.Parse(vehicleData.OdometerMultiplier, NumberStyles.Any));
|
||||||
|
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Adjusted odometer for multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
public IActionResult DuplicateRecords(List<int> recordIds, ImportMode importMode)
|
public IActionResult DuplicateRecords(List<int> recordIds, ImportMode importMode)
|
||||||
@@ -2266,6 +2611,10 @@ namespace CarCareTracker.Controllers
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}");
|
||||||
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -2390,7 +2739,31 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveUserColumnPreferences(UserColumnPreference columnPreference)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
var existingUserColumnPreference = userConfig.UserColumnPreferences.Where(x => x.Tab == columnPreference.Tab);
|
||||||
|
if (existingUserColumnPreference.Any())
|
||||||
|
{
|
||||||
|
var existingPreference = existingUserColumnPreference.Single();
|
||||||
|
existingPreference.VisibleColumns = columnPreference.VisibleColumns;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userConfig.UserColumnPreferences.Add(columnPreference);
|
||||||
|
}
|
||||||
|
var result = _config.SaveUserConfig(User, userConfig);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
SupplyRecord = 7,
|
SupplyRecord = 7,
|
||||||
Dashboard = 8,
|
Dashboard = 8,
|
||||||
PlanRecord = 9,
|
PlanRecord = 9,
|
||||||
OdometerRecord = 10
|
OdometerRecord = 10,
|
||||||
|
VehicleRecord = 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ namespace CarCareTracker.Helper
|
|||||||
public interface IConfigHelper
|
public interface IConfigHelper
|
||||||
{
|
{
|
||||||
OpenIDConfig GetOpenIDConfig();
|
OpenIDConfig GetOpenIDConfig();
|
||||||
|
ReminderUrgencyConfig GetReminderUrgencyConfig();
|
||||||
UserConfig GetUserConfig(ClaimsPrincipal user);
|
UserConfig GetUserConfig(ClaimsPrincipal user);
|
||||||
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
|
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
|
||||||
|
bool AuthenticateRootUser(string username, string password);
|
||||||
|
string GetWebHookUrl();
|
||||||
|
string GetMOTD();
|
||||||
string GetLogoUrl();
|
string GetLogoUrl();
|
||||||
string GetServerLanguage();
|
string GetServerLanguage();
|
||||||
bool GetServerEnableShopSupplies();
|
bool GetServerEnableShopSupplies();
|
||||||
string GetServerPostgresConnection();
|
string GetServerPostgresConnection();
|
||||||
|
string GetAllowedFileUploadExtensions();
|
||||||
public bool DeleteUserConfig(int userId);
|
public bool DeleteUserConfig(int userId);
|
||||||
}
|
}
|
||||||
public class ConfigHelper : IConfigHelper
|
public class ConfigHelper : IConfigHelper
|
||||||
@@ -29,11 +34,34 @@ namespace CarCareTracker.Helper
|
|||||||
_userConfig = userConfig;
|
_userConfig = userConfig;
|
||||||
_cache = memoryCache;
|
_cache = memoryCache;
|
||||||
}
|
}
|
||||||
|
public string GetWebHookUrl()
|
||||||
|
{
|
||||||
|
var webhook = _config["LUBELOGGER_WEBHOOK"];
|
||||||
|
if (string.IsNullOrWhiteSpace(webhook))
|
||||||
|
{
|
||||||
|
webhook = "";
|
||||||
|
}
|
||||||
|
return webhook;
|
||||||
|
}
|
||||||
|
public string GetMOTD()
|
||||||
|
{
|
||||||
|
var motd = _config["LUBELOGGER_MOTD"];
|
||||||
|
if (string.IsNullOrWhiteSpace(motd))
|
||||||
|
{
|
||||||
|
motd = "";
|
||||||
|
}
|
||||||
|
return motd;
|
||||||
|
}
|
||||||
public OpenIDConfig GetOpenIDConfig()
|
public OpenIDConfig GetOpenIDConfig()
|
||||||
{
|
{
|
||||||
OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig();
|
OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig();
|
||||||
return openIdConfig;
|
return openIdConfig;
|
||||||
}
|
}
|
||||||
|
public ReminderUrgencyConfig GetReminderUrgencyConfig()
|
||||||
|
{
|
||||||
|
ReminderUrgencyConfig reminderUrgencyConfig = _config.GetSection("ReminderUrgencyConfig").Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig();
|
||||||
|
return reminderUrgencyConfig;
|
||||||
|
}
|
||||||
public string GetLogoUrl()
|
public string GetLogoUrl()
|
||||||
{
|
{
|
||||||
var logoUrl = _config["LUBELOGGER_LOGO_URL"];
|
var logoUrl = _config["LUBELOGGER_LOGO_URL"];
|
||||||
@@ -43,6 +71,24 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
return logoUrl;
|
return logoUrl;
|
||||||
}
|
}
|
||||||
|
public string GetAllowedFileUploadExtensions()
|
||||||
|
{
|
||||||
|
var allowedFileExtensions = _config["LUBELOGGER_ALLOWED_FILE_EXTENSIONS"];
|
||||||
|
if (string.IsNullOrWhiteSpace(allowedFileExtensions)){
|
||||||
|
return StaticHelper.DefaultAllowedFileExtensions;
|
||||||
|
}
|
||||||
|
return allowedFileExtensions;
|
||||||
|
}
|
||||||
|
public bool AuthenticateRootUser(string username, string password)
|
||||||
|
{
|
||||||
|
var rootUsername = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
|
||||||
|
var rootPassword = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(rootUsername) || string.IsNullOrWhiteSpace(rootPassword))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return username == rootUsername && password == rootPassword;
|
||||||
|
}
|
||||||
public string GetServerLanguage()
|
public string GetServerLanguage()
|
||||||
{
|
{
|
||||||
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||||
@@ -81,20 +127,9 @@ namespace CarCareTracker.Helper
|
|||||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
|
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
|
||||||
}
|
}
|
||||||
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||||
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false");
|
||||||
if (existingUserConfig is not null)
|
configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
|
||||||
{
|
configData.UserPasswordHash = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
|
||||||
//copy over settings that are off limits on the settings page.
|
|
||||||
configData.EnableAuth = existingUserConfig.EnableAuth;
|
|
||||||
configData.UserNameHash = existingUserConfig.UserNameHash;
|
|
||||||
configData.UserPasswordHash = existingUserConfig.UserPasswordHash;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
configData.EnableAuth = false;
|
|
||||||
configData.UserNameHash = string.Empty;
|
|
||||||
configData.UserPasswordHash = string.Empty;
|
|
||||||
}
|
|
||||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData));
|
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData));
|
||||||
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
||||||
return true;
|
return true;
|
||||||
@@ -139,8 +174,12 @@ namespace CarCareTracker.Helper
|
|||||||
PreferredGasMileageUnit = _config[nameof(UserConfig.PreferredGasMileageUnit)],
|
PreferredGasMileageUnit = _config[nameof(UserConfig.PreferredGasMileageUnit)],
|
||||||
PreferredGasUnit = _config[nameof(UserConfig.PreferredGasUnit)],
|
PreferredGasUnit = _config[nameof(UserConfig.PreferredGasUnit)],
|
||||||
UserLanguage = _config[nameof(UserConfig.UserLanguage)],
|
UserLanguage = _config[nameof(UserConfig.UserLanguage)],
|
||||||
|
HideSoldVehicles = bool.Parse(_config[nameof(UserConfig.HideSoldVehicles)]),
|
||||||
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
|
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
|
||||||
VisibleTabs = _config.GetSection("VisibleTabs").Get<List<ImportMode>>(),
|
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
|
||||||
|
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(),
|
||||||
|
UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(),
|
||||||
|
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
|
||||||
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)])
|
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)])
|
||||||
};
|
};
|
||||||
int userId = 0;
|
int userId = 0;
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ namespace CarCareTracker.Helper
|
|||||||
//copy over images and documents.
|
//copy over images and documents.
|
||||||
var imagePath = Path.Combine(tempPath, "images");
|
var imagePath = Path.Combine(tempPath, "images");
|
||||||
var documentPath = Path.Combine(tempPath, "documents");
|
var documentPath = Path.Combine(tempPath, "documents");
|
||||||
|
var translationPath = Path.Combine(tempPath, "translations");
|
||||||
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
||||||
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
||||||
if (Directory.Exists(imagePath))
|
if (Directory.Exists(imagePath))
|
||||||
@@ -139,6 +140,28 @@ namespace CarCareTracker.Helper
|
|||||||
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
|
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Directory.Exists(translationPath))
|
||||||
|
{
|
||||||
|
var existingPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||||
|
if (!Directory.Exists(existingPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(existingPath);
|
||||||
|
}
|
||||||
|
else if (clearExisting)
|
||||||
|
{
|
||||||
|
var filesToDelete = Directory.GetFiles(existingPath);
|
||||||
|
foreach (string file in filesToDelete)
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//copy each files from temp folder to newPath
|
||||||
|
var filesToUpload = Directory.GetFiles(translationPath);
|
||||||
|
foreach (string file in filesToUpload)
|
||||||
|
{
|
||||||
|
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (File.Exists(dataPath))
|
if (File.Exists(dataPath))
|
||||||
{
|
{
|
||||||
//data path will always exist as it is created on startup if not.
|
//data path will always exist as it is created on startup if not.
|
||||||
@@ -173,7 +196,7 @@ namespace CarCareTracker.Helper
|
|||||||
foreach (UploadedFiles file in reportModel.Files)
|
foreach (UploadedFiles file in reportModel.Files)
|
||||||
{
|
{
|
||||||
var fileToCopy = GetFullFilePath(file.Location);
|
var fileToCopy = GetFullFilePath(file.Location);
|
||||||
var destFileName = $"{tempPath}/{fileIndex}{Path.GetExtension(file.Location)}";
|
var destFileName = $"{tempPath}/{fileIndex}_{reportModel.DataType}_{reportModel.Date.ToString("yyyy-MM-dd")}_{file.Name}{Path.GetExtension(file.Location)}";
|
||||||
File.Copy(fileToCopy, destFileName);
|
File.Copy(fileToCopy, destFileName);
|
||||||
fileIndex++;
|
fileIndex++;
|
||||||
}
|
}
|
||||||
@@ -191,6 +214,7 @@ namespace CarCareTracker.Helper
|
|||||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
||||||
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
|
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
|
||||||
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||||
|
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||||
var dataPath = StaticHelper.DbName;
|
var dataPath = StaticHelper.DbName;
|
||||||
var configPath = StaticHelper.UserConfigPath;
|
var configPath = StaticHelper.UserConfigPath;
|
||||||
if (!Directory.Exists(tempPath))
|
if (!Directory.Exists(tempPath))
|
||||||
@@ -215,6 +239,16 @@ namespace CarCareTracker.Helper
|
|||||||
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
|
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Directory.Exists(translationPath))
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(translationPath);
|
||||||
|
foreach(var file in files)
|
||||||
|
{
|
||||||
|
var newPath = Path.Combine(tempPath, "translations");
|
||||||
|
Directory.CreateDirectory(newPath);
|
||||||
|
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (File.Exists(dataPath))
|
if (File.Exists(dataPath))
|
||||||
{
|
{
|
||||||
var newPath = Path.Combine(tempPath, "data");
|
var newPath = Path.Combine(tempPath, "data");
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ namespace CarCareTracker.Helper
|
|||||||
IsFillToFull = currentObject.IsFillToFull,
|
IsFillToFull = currentObject.IsFillToFull,
|
||||||
MissedFuelUp = currentObject.MissedFuelUp,
|
MissedFuelUp = currentObject.MissedFuelUp,
|
||||||
Notes = currentObject.Notes,
|
Notes = currentObject.Notes,
|
||||||
Tags = currentObject.Tags
|
Tags = currentObject.Tags,
|
||||||
|
ExtraFields = currentObject.ExtraFields
|
||||||
};
|
};
|
||||||
if (currentObject.MissedFuelUp)
|
if (currentObject.MissedFuelUp)
|
||||||
{
|
{
|
||||||
@@ -124,7 +125,8 @@ namespace CarCareTracker.Helper
|
|||||||
IsFillToFull = currentObject.IsFillToFull,
|
IsFillToFull = currentObject.IsFillToFull,
|
||||||
MissedFuelUp = currentObject.MissedFuelUp,
|
MissedFuelUp = currentObject.MissedFuelUp,
|
||||||
Notes = currentObject.Notes,
|
Notes = currentObject.Notes,
|
||||||
Tags = currentObject.Tags
|
Tags = currentObject.Tags,
|
||||||
|
ExtraFields = currentObject.ExtraFields
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
previousMileage = currentObject.Mileage;
|
previousMileage = currentObject.Mileage;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
OperationResponse NotifyUserForRegistration(string emailAddress, string token);
|
OperationResponse NotifyUserForRegistration(string emailAddress, string token);
|
||||||
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
||||||
|
OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token);
|
||||||
OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders);
|
OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders);
|
||||||
}
|
}
|
||||||
public class MailHelper : IMailHelper
|
public class MailHelper : IMailHelper
|
||||||
@@ -64,6 +65,28 @@ namespace CarCareTracker.Helper
|
|||||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
|
||||||
|
}
|
||||||
|
string emailSubject = "Your User Account Update Token for LubeLogger";
|
||||||
|
string emailBody = $"A token has been generated on your behalf, please update your account for LubeLogger using the token: {token}";
|
||||||
|
var result = SendEmail(emailAddress, emailSubject, emailBody);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
public OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders)
|
public OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
public class ReminderHelper: IReminderHelper
|
public class ReminderHelper: IReminderHelper
|
||||||
{
|
{
|
||||||
|
private readonly IConfigHelper _config;
|
||||||
|
public ReminderHelper(IConfigHelper config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder)
|
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder)
|
||||||
{
|
{
|
||||||
if (existingReminder.Metric == ReminderMetric.Both)
|
if (existingReminder.Metric == ReminderMetric.Both)
|
||||||
@@ -56,6 +61,7 @@ namespace CarCareTracker.Helper
|
|||||||
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare)
|
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare)
|
||||||
{
|
{
|
||||||
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
||||||
|
var reminderUrgencyConfig = _config.GetReminderUrgencyConfig();
|
||||||
foreach (var reminder in reminders)
|
foreach (var reminder in reminders)
|
||||||
{
|
{
|
||||||
var reminderViewModel = new ReminderRecordViewModel()
|
var reminderViewModel = new ReminderRecordViewModel()
|
||||||
@@ -81,24 +87,24 @@ namespace CarCareTracker.Helper
|
|||||||
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
}
|
}
|
||||||
else if (reminder.Date < dateCompare.AddDays(7))
|
else if (reminder.Date < dateCompare.AddDays(reminderUrgencyConfig.VeryUrgentDays))
|
||||||
{
|
{
|
||||||
//if less than a week from today or less than 50 miles from current mileage then very urgent.
|
//if less than a week from today or less than 50 miles from current mileage then very urgent.
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
//have to specify by which metric this reminder is urgent.
|
//have to specify by which metric this reminder is urgent.
|
||||||
reminderViewModel.Metric = ReminderMetric.Date;
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
}
|
}
|
||||||
else if (reminder.Mileage < currentMileage + 50)
|
else if (reminder.Mileage < currentMileage + reminderUrgencyConfig.VeryUrgentDistance)
|
||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
}
|
}
|
||||||
else if (reminder.Date < dateCompare.AddDays(30))
|
else if (reminder.Date < dateCompare.AddDays(reminderUrgencyConfig.UrgentDays))
|
||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
reminderViewModel.Metric = ReminderMetric.Date;
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
}
|
}
|
||||||
else if (reminder.Mileage < currentMileage + 100)
|
else if (reminder.Mileage < currentMileage + reminderUrgencyConfig.UrgentDistance)
|
||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
@@ -110,11 +116,11 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
}
|
}
|
||||||
else if (reminder.Date < dateCompare.AddDays(7))
|
else if (reminder.Date < dateCompare.AddDays(reminderUrgencyConfig.VeryUrgentDays))
|
||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
}
|
}
|
||||||
else if (reminder.Date < dateCompare.AddDays(30))
|
else if (reminder.Date < dateCompare.AddDays(reminderUrgencyConfig.UrgentDays))
|
||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
}
|
}
|
||||||
@@ -126,11 +132,11 @@ namespace CarCareTracker.Helper
|
|||||||
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
}
|
}
|
||||||
else if (reminder.Mileage < currentMileage + 50)
|
else if (reminder.Mileage < currentMileage + reminderUrgencyConfig.VeryUrgentDistance)
|
||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
}
|
}
|
||||||
else if (reminder.Mileage < currentMileage + 100)
|
else if (reminder.Mileage < currentMileage + reminderUrgencyConfig.UrgentDistance)
|
||||||
{
|
{
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ namespace CarCareTracker.Helper
|
|||||||
MonthId = x.Key,
|
MonthId = x.Key,
|
||||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
Cost = 0,
|
Cost = 0,
|
||||||
MaxMileage = x.Max(y => y.Mileage),
|
DistanceTraveled = x.Sum(y=>y.DistanceTraveled)
|
||||||
MinMileage = x.Min(y => y.Mileage)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
|
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
|
||||||
@@ -39,9 +38,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
MonthId = x.Key,
|
MonthId = x.Key,
|
||||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
Cost = x.Sum(y => y.Cost),
|
Cost = x.Sum(y => y.Cost)
|
||||||
MaxMileage = x.Max(y=>y.Mileage),
|
|
||||||
MinMileage = x.Min(y=>y.Mileage)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
|
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
|
||||||
@@ -54,9 +51,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
MonthId = x.Key,
|
MonthId = x.Key,
|
||||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
Cost = x.Sum(y => y.Cost),
|
Cost = x.Sum(y => y.Cost)
|
||||||
MaxMileage = x.Max(y => y.Mileage),
|
|
||||||
MinMileage = x.Min(y => y.Mileage)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
|
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
|
||||||
@@ -69,9 +64,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
MonthId = x.Key,
|
MonthId = x.Key,
|
||||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
Cost = x.Sum(y => y.Cost),
|
Cost = x.Sum(y => y.Cost)
|
||||||
MaxMileage = x.Max(y => y.Mileage),
|
|
||||||
MinMileage = x.Min(y => y.Mileage)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
|
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
|
||||||
@@ -84,9 +77,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
MonthId = x.Key,
|
MonthId = x.Key,
|
||||||
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
Cost = x.Sum(y => y.Cost),
|
Cost = x.Sum(y => y.Cost)
|
||||||
MaxMileage = x.Max(y => y.Mileage),
|
|
||||||
MinMileage = x.Min(y => y.Mileage)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)
|
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ namespace CarCareTracker.Helper
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StaticHelper
|
public static class StaticHelper
|
||||||
{
|
{
|
||||||
|
public static string VersionNumber = "1.2.7";
|
||||||
public static string DbName = "data/cartracker.db";
|
public static string DbName = "data/cartracker.db";
|
||||||
public static string UserConfigPath = "config/userConfig.json";
|
public static string UserConfigPath = "config/userConfig.json";
|
||||||
public static string GenericErrorMessage = "An error occurred, please try again later";
|
public static string GenericErrorMessage = "An error occurred, please try again later";
|
||||||
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
||||||
|
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
||||||
|
|
||||||
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
||||||
{
|
{
|
||||||
@@ -182,11 +184,20 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
return templateExtraFields;
|
return templateExtraFields;
|
||||||
}
|
}
|
||||||
//append the fields.
|
var recordFieldNames = recordExtraFields.Select(x => x.Name);
|
||||||
|
//update isrequired setting
|
||||||
foreach (ExtraField extraField in recordExtraFields)
|
foreach (ExtraField extraField in recordExtraFields)
|
||||||
{
|
{
|
||||||
extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired;
|
extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired;
|
||||||
}
|
}
|
||||||
|
//append extra fields
|
||||||
|
foreach(ExtraField extraField in templateExtraFields)
|
||||||
|
{
|
||||||
|
if (!recordFieldNames.Contains(extraField.Name))
|
||||||
|
{
|
||||||
|
recordExtraFields.Add(extraField);
|
||||||
|
}
|
||||||
|
}
|
||||||
return recordExtraFields;
|
return recordExtraFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,5 +227,37 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
return new DateTimeOffset(date).ToUnixTimeMilliseconds();
|
return new DateTimeOffset(date).ToUnixTimeMilliseconds();
|
||||||
}
|
}
|
||||||
|
public static void InitMessage(IConfiguration config)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"LubeLogger {VersionNumber}");
|
||||||
|
Console.WriteLine("Website: https://lubelogger.com");
|
||||||
|
Console.WriteLine("Documentation: https://docs.lubelogger.com");
|
||||||
|
Console.WriteLine("GitHub: https://github.com/hargata/lubelog");
|
||||||
|
var mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
|
||||||
|
if (mailConfig != null && !string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"SMTP Configured for {mailConfig.EmailServer}");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
Console.WriteLine("SMTP Not Configured");
|
||||||
|
}
|
||||||
|
var motd = config["LUBELOGGER_MOTD"] ?? "Not Configured";
|
||||||
|
Console.WriteLine($"Message Of The Day: {motd}");
|
||||||
|
}
|
||||||
|
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(webhookURL))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
var httpParams = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "vehicleId", vehicleId.ToString() },
|
||||||
|
{ "username", username },
|
||||||
|
{ "action", action },
|
||||||
|
};
|
||||||
|
httpClient.PostAsJsonAsync(webhookURL, httpParams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace CarCareTracker.Logic
|
|||||||
bool DeleteUserToken(int tokenId);
|
bool DeleteUserToken(int tokenId);
|
||||||
bool DeleteUser(int userId);
|
bool DeleteUser(int userId);
|
||||||
OperationResponse RegisterOpenIdUser(LoginModel credentials);
|
OperationResponse RegisterOpenIdUser(LoginModel credentials);
|
||||||
|
OperationResponse UpdateUserDetails(int userId, LoginModel credentials);
|
||||||
OperationResponse RegisterNewUser(LoginModel credentials);
|
OperationResponse RegisterNewUser(LoginModel credentials);
|
||||||
OperationResponse RequestResetPassword(LoginModel credentials);
|
OperationResponse RequestResetPassword(LoginModel credentials);
|
||||||
OperationResponse ResetPasswordByUser(LoginModel credentials);
|
OperationResponse ResetPasswordByUser(LoginModel credentials);
|
||||||
@@ -24,6 +25,7 @@ namespace CarCareTracker.Logic
|
|||||||
bool CheckIfUserIsValid(int userId);
|
bool CheckIfUserIsValid(int userId);
|
||||||
bool CreateRootUserCredentials(LoginModel credentials);
|
bool CreateRootUserCredentials(LoginModel credentials);
|
||||||
bool DeleteRootUserCredentials();
|
bool DeleteRootUserCredentials();
|
||||||
|
bool GenerateTokenForEmailAddress(string emailAddress, bool isPasswordReset);
|
||||||
List<UserData> GetAllUsers();
|
List<UserData> GetAllUsers();
|
||||||
List<Token> GetAllTokens();
|
List<Token> GetAllTokens();
|
||||||
|
|
||||||
@@ -33,15 +35,18 @@ namespace CarCareTracker.Logic
|
|||||||
private readonly IUserRecordDataAccess _userData;
|
private readonly IUserRecordDataAccess _userData;
|
||||||
private readonly ITokenRecordDataAccess _tokenData;
|
private readonly ITokenRecordDataAccess _tokenData;
|
||||||
private readonly IMailHelper _mailHelper;
|
private readonly IMailHelper _mailHelper;
|
||||||
|
private readonly IConfigHelper _configHelper;
|
||||||
private IMemoryCache _cache;
|
private IMemoryCache _cache;
|
||||||
public LoginLogic(IUserRecordDataAccess userData,
|
public LoginLogic(IUserRecordDataAccess userData,
|
||||||
ITokenRecordDataAccess tokenData,
|
ITokenRecordDataAccess tokenData,
|
||||||
IMailHelper mailHelper,
|
IMailHelper mailHelper,
|
||||||
|
IConfigHelper configHelper,
|
||||||
IMemoryCache memoryCache)
|
IMemoryCache memoryCache)
|
||||||
{
|
{
|
||||||
_userData = userData;
|
_userData = userData;
|
||||||
_tokenData = tokenData;
|
_tokenData = tokenData;
|
||||||
_mailHelper = mailHelper;
|
_mailHelper = mailHelper;
|
||||||
|
_configHelper = configHelper;
|
||||||
_cache = memoryCache;
|
_cache = memoryCache;
|
||||||
}
|
}
|
||||||
public bool CheckIfUserIsValid(int userId)
|
public bool CheckIfUserIsValid(int userId)
|
||||||
@@ -59,6 +64,50 @@ namespace CarCareTracker.Logic
|
|||||||
return result.Id != 0;
|
return result.Id != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public OperationResponse UpdateUserDetails(int userId, LoginModel credentials)
|
||||||
|
{
|
||||||
|
//get current user details
|
||||||
|
var existingUser = _userData.GetUserRecordById(userId);
|
||||||
|
if (existingUser.Id == default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Invalid user" };
|
||||||
|
}
|
||||||
|
//validate user token
|
||||||
|
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
|
||||||
|
if (existingToken.Id == default || existingToken.EmailAddress != existingUser.EmailAddress)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Invalid Token" };
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(credentials.UserName) && existingUser.UserName != credentials.UserName)
|
||||||
|
{
|
||||||
|
//check if new username is already taken.
|
||||||
|
var existingUserWithUserName = _userData.GetUserRecordByUserName(credentials.UserName);
|
||||||
|
if (existingUserWithUserName.Id != default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Username already taken" };
|
||||||
|
}
|
||||||
|
existingUser.UserName = credentials.UserName;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(credentials.EmailAddress) && existingUser.EmailAddress != credentials.EmailAddress)
|
||||||
|
{
|
||||||
|
//check if email address already exists
|
||||||
|
var existingUserWithEmailAddress = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
|
||||||
|
if (existingUserWithEmailAddress.Id != default)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
|
||||||
|
}
|
||||||
|
existingUser.EmailAddress = credentials.EmailAddress;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(credentials.Password))
|
||||||
|
{
|
||||||
|
//update password
|
||||||
|
existingUser.Password = GetHash(credentials.Password);
|
||||||
|
}
|
||||||
|
//delete token
|
||||||
|
_tokenData.DeleteToken(existingToken.Id);
|
||||||
|
var result = _userData.SaveUserRecord(existingUser);
|
||||||
|
return new OperationResponse { Success = result, Message = result ? "User Updated" : StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
public OperationResponse RegisterOpenIdUser(LoginModel credentials)
|
public OperationResponse RegisterOpenIdUser(LoginModel credentials)
|
||||||
{
|
{
|
||||||
//validate their token.
|
//validate their token.
|
||||||
@@ -151,21 +200,7 @@ namespace CarCareTracker.Logic
|
|||||||
if (existingUser.Id != default)
|
if (existingUser.Id != default)
|
||||||
{
|
{
|
||||||
//user exists, generate a token and send email.
|
//user exists, generate a token and send email.
|
||||||
//check to see if there is an existing token sent to the user.
|
GenerateTokenForEmailAddress(existingUser.EmailAddress, true);
|
||||||
var existingToken = _tokenData.GetTokenRecordByEmailAddress(existingUser.EmailAddress);
|
|
||||||
if (existingToken.Id == default)
|
|
||||||
{
|
|
||||||
var token = new Token()
|
|
||||||
{
|
|
||||||
Body = NewToken(),
|
|
||||||
EmailAddress = existingUser.EmailAddress
|
|
||||||
};
|
|
||||||
var result = _tokenData.CreateNewToken(token);
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
result = _mailHelper.NotifyUserForPasswordReset(existingUser.EmailAddress, token.Body).Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//for security purposes we want to always return true for this method.
|
//for security purposes we want to always return true for this method.
|
||||||
//otherwise someone can spam the reset password method to sniff out users.
|
//otherwise someone can spam the reset password method to sniff out users.
|
||||||
@@ -214,7 +249,8 @@ namespace CarCareTracker.Logic
|
|||||||
Id = -1,
|
Id = -1,
|
||||||
UserName = credentials.UserName,
|
UserName = credentials.UserName,
|
||||||
IsAdmin = true,
|
IsAdmin = true,
|
||||||
IsRootUser = true
|
IsRootUser = true,
|
||||||
|
EmailAddress = string.Empty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -379,21 +415,9 @@ namespace CarCareTracker.Logic
|
|||||||
}
|
}
|
||||||
private bool UserIsRoot(LoginModel credentials)
|
private bool UserIsRoot(LoginModel credentials)
|
||||||
{
|
{
|
||||||
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
var hashedUserName = GetHash(credentials.UserName);
|
||||||
var existingUserConfig = JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
var hashedPassword = GetHash(credentials.Password);
|
||||||
if (existingUserConfig is not null)
|
return _configHelper.AuthenticateRootUser(hashedUserName, hashedPassword);
|
||||||
{
|
|
||||||
//create hashes of the login credentials.
|
|
||||||
var hashedUserName = GetHash(credentials.UserName);
|
|
||||||
var hashedPassword = GetHash(credentials.Password);
|
|
||||||
//compare against stored hash.
|
|
||||||
if (hashedUserName == existingUserConfig.UserNameHash &&
|
|
||||||
hashedPassword == existingUserConfig.UserPasswordHash)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
private static string GetHash(string value)
|
private static string GetHash(string value)
|
||||||
@@ -415,5 +439,30 @@ namespace CarCareTracker.Logic
|
|||||||
{
|
{
|
||||||
return Guid.NewGuid().ToString().Substring(0, 8);
|
return Guid.NewGuid().ToString().Substring(0, 8);
|
||||||
}
|
}
|
||||||
|
public bool GenerateTokenForEmailAddress(string emailAddress, bool isPasswordReset)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
//check if there is already a token tied to this email address.
|
||||||
|
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
|
||||||
|
if (existingToken.Id == default)
|
||||||
|
{
|
||||||
|
//no token, generate one and send.
|
||||||
|
var token = new Token()
|
||||||
|
{
|
||||||
|
Body = NewToken(),
|
||||||
|
EmailAddress = emailAddress
|
||||||
|
};
|
||||||
|
result = _tokenData.CreateNewToken(token);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
result = isPasswordReset ? _mailHelper.NotifyUserForPasswordReset(emailAddress, token.Body).Success : _mailHelper.NotifyUserForAccountUpdate(emailAddress, token.Body).Success;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
//token exists, send it again.
|
||||||
|
result = isPasswordReset ? _mailHelper.NotifyUserForPasswordReset(emailAddress, existingToken.Body).Success : _mailHelper.NotifyUserForAccountUpdate(emailAddress, existingToken.Body).Success;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
Logic/OdometerLogic.cs
Normal file
67
Logic/OdometerLogic.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Logic
|
||||||
|
{
|
||||||
|
public interface IOdometerLogic
|
||||||
|
{
|
||||||
|
int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords);
|
||||||
|
bool AutoInsertOdometerRecord(OdometerRecord odometer);
|
||||||
|
List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords);
|
||||||
|
}
|
||||||
|
public class OdometerLogic: IOdometerLogic
|
||||||
|
{
|
||||||
|
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
||||||
|
private readonly ILogger<IOdometerLogic> _logger;
|
||||||
|
public OdometerLogic(IOdometerRecordDataAccess odometerRecordDataAccess, ILogger<IOdometerLogic> logger)
|
||||||
|
{
|
||||||
|
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
public int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords)
|
||||||
|
{
|
||||||
|
if (!odometerRecords.Any())
|
||||||
|
{
|
||||||
|
odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
}
|
||||||
|
if (!odometerRecords.Any())
|
||||||
|
{
|
||||||
|
//no existing odometer records for this vehicle.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return odometerRecords.Max(x => x.Mileage);
|
||||||
|
}
|
||||||
|
public bool AutoInsertOdometerRecord(OdometerRecord odometer)
|
||||||
|
{
|
||||||
|
var lastReportedMileage = GetLastOdometerRecordMileage(odometer.VehicleId, new List<OdometerRecord>());
|
||||||
|
odometer.InitialMileage = lastReportedMileage != default ? lastReportedMileage : odometer.Mileage;
|
||||||
|
|
||||||
|
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometer);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords)
|
||||||
|
{
|
||||||
|
//perform ordering
|
||||||
|
odometerRecords = odometerRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
|
int previousMileage = 0;
|
||||||
|
for (int i = 0; i < odometerRecords.Count; i++)
|
||||||
|
{
|
||||||
|
var currentObject = odometerRecords[i];
|
||||||
|
if (previousMileage == default)
|
||||||
|
{
|
||||||
|
//first record
|
||||||
|
currentObject.InitialMileage = currentObject.Mileage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//subsequent records
|
||||||
|
currentObject.InitialMileage = previousMileage;
|
||||||
|
}
|
||||||
|
//save to db.
|
||||||
|
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(currentObject);
|
||||||
|
previousMileage = currentObject.Mileage;
|
||||||
|
}
|
||||||
|
return odometerRecords;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ namespace CarCareTracker.MapProfile
|
|||||||
Map(m => m.Date).Name(["date", "fuelup_date"]);
|
Map(m => m.Date).Name(["date", "fuelup_date"]);
|
||||||
Map(m => m.DateCreated).Name(["datecreated"]);
|
Map(m => m.DateCreated).Name(["datecreated"]);
|
||||||
Map(m => m.DateModified).Name(["datemodified"]);
|
Map(m => m.DateModified).Name(["datemodified"]);
|
||||||
|
Map(m => m.InitialOdometer).Name(["initialodometer"]);
|
||||||
Map(m => m.Odometer).Name(["odometer"]);
|
Map(m => m.Odometer).Name(["odometer"]);
|
||||||
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
|
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
|
||||||
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
|
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ namespace CarCareTracker.Middleware
|
|||||||
{
|
{
|
||||||
new(ClaimTypes.Name, authCookie.UserData.UserName),
|
new(ClaimTypes.Name, authCookie.UserData.UserName),
|
||||||
new(ClaimTypes.NameIdentifier, authCookie.UserData.Id.ToString()),
|
new(ClaimTypes.NameIdentifier, authCookie.UserData.Id.ToString()),
|
||||||
|
new(ClaimTypes.Email, authCookie.UserData.EmailAddress),
|
||||||
new(ClaimTypes.Role, "CookieAuth")
|
new(ClaimTypes.Role, "CookieAuth")
|
||||||
};
|
};
|
||||||
if (authCookie.UserData.IsAdmin)
|
if (authCookie.UserData.IsAdmin)
|
||||||
|
|||||||
8
Models/GasRecord/GasRecordEditModel.cs
Normal file
8
Models/GasRecord/GasRecordEditModel.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class GasRecordEditModel
|
||||||
|
{
|
||||||
|
public List<int> RecordIds { get; set; } = new List<int>();
|
||||||
|
public GasRecord EditRecord { get; set; } = new GasRecord();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
public bool MissedFuelUp { get; set; }
|
public bool MissedFuelUp { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp); } }
|
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp); } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
public string TokenURL { get; set; }
|
public string TokenURL { get; set; }
|
||||||
public string RedirectURL { get; set; }
|
public string RedirectURL { get; set; }
|
||||||
public string Scope { get; set; }
|
public string Scope { get; set; }
|
||||||
public string RemoteAuthURL { get { return $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}"; } }
|
public string State { get; set; }
|
||||||
|
public bool ValidateState { get; set; } = false;
|
||||||
|
public string RemoteAuthURL { get { return $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}"; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
|
public int InitialMileage { get; set; }
|
||||||
public int Mileage { get; set; }
|
public int Mileage { get; set; }
|
||||||
|
public int DistanceTraveled { get { return Mileage - InitialMileage; } }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
|||||||
8
Models/OdometerRecord/OdometerRecordEditModel.cs
Normal file
8
Models/OdometerRecord/OdometerRecordEditModel.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class OdometerRecordEditModel
|
||||||
|
{
|
||||||
|
public List<int> RecordIds { get; set; } = new List<int>();
|
||||||
|
public OdometerRecord EditRecord { get; set; } = new OdometerRecord();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,12 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||||
|
public int InitialMileage { get; set; }
|
||||||
public int Mileage { get; set; }
|
public int Mileage { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
|
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields, InitialMileage = InitialMileage }; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
Models/Reminder/ReminderConfig.cs
Normal file
10
Models/Reminder/ReminderConfig.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReminderUrgencyConfig
|
||||||
|
{
|
||||||
|
public int UrgentDays { get; set; } = 30;
|
||||||
|
public int VeryUrgentDays { get; set; } = 7;
|
||||||
|
public int UrgentDistance { get; set; } = 100;
|
||||||
|
public int VeryUrgentDistance { get; set; } = 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,6 @@
|
|||||||
public int MonthId { get; set; }
|
public int MonthId { get; set; }
|
||||||
public string MonthName { get; set; }
|
public string MonthName { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public int MaxMileage { get; set; }
|
|
||||||
public int MinMileage { get; set; }
|
|
||||||
public int DistanceTraveled { get; set; }
|
public int DistanceTraveled { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,10 @@
|
|||||||
public string MPG { get; set; }
|
public string MPG { get; set; }
|
||||||
public decimal TotalCost { get; set; }
|
public decimal TotalCost { get; set; }
|
||||||
public decimal TotalGasCost { get; set; }
|
public decimal TotalGasCost { get; set; }
|
||||||
|
public string DaysOwned { get; set; }
|
||||||
|
public string DistanceTraveled { get; set; }
|
||||||
|
public decimal TotalCostPerMile { get; set; }
|
||||||
|
public decimal TotalGasCostPerMile { get; set; }
|
||||||
|
public string DistanceUnit { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public string Priority { get; set; }
|
public string Priority { get; set; }
|
||||||
public string Progress { get; set; }
|
public string Progress { get; set; }
|
||||||
|
public string InitialOdometer { get; set; }
|
||||||
public string Odometer { get; set; }
|
public string Odometer { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
public class OdometerRecordExportModel
|
public class OdometerRecordExportModel
|
||||||
{
|
{
|
||||||
public string Date { get; set; }
|
public string Date { get; set; }
|
||||||
|
public string InitialOdometer { get; set; }
|
||||||
public string Odometer { get; set; }
|
public string Odometer { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public string Tags { get; set; }
|
public string Tags { get; set; }
|
||||||
|
|||||||
8
Models/User/UserColumnPreference.cs
Normal file
8
Models/User/UserColumnPreference.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UserColumnPreference
|
||||||
|
{
|
||||||
|
public ImportMode Tab { get; set; }
|
||||||
|
public List<string> VisibleColumns { get; set; } = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,12 @@
|
|||||||
public bool EnableAutoReminderRefresh { get; set; }
|
public bool EnableAutoReminderRefresh { get; set; }
|
||||||
public bool EnableAutoOdometerInsert { get; set; }
|
public bool EnableAutoOdometerInsert { get; set; }
|
||||||
public bool EnableShopSupplies { get; set; }
|
public bool EnableShopSupplies { get; set; }
|
||||||
|
public bool EnableExtraFieldColumns { get; set; }
|
||||||
|
public bool HideSoldVehicles { get; set; }
|
||||||
public string PreferredGasUnit { get; set; } = string.Empty;
|
public string PreferredGasUnit { get; set; } = string.Empty;
|
||||||
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
||||||
|
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
|
||||||
|
public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig();
|
||||||
public string UserNameHash { get; set; }
|
public string UserNameHash { get; set; }
|
||||||
public string UserPasswordHash { get; set;}
|
public string UserPasswordHash { get; set;}
|
||||||
public string UserLanguage { get; set; } = "en_US";
|
public string UserLanguage { get; set; } = "en_US";
|
||||||
|
|||||||
@@ -8,8 +8,20 @@
|
|||||||
public string Make { get; set; }
|
public string Make { get; set; }
|
||||||
public string Model { get; set; }
|
public string Model { get; set; }
|
||||||
public string LicensePlate { get; set; }
|
public string LicensePlate { get; set; }
|
||||||
|
public string PurchaseDate { get; set; }
|
||||||
|
public string SoldDate { get; set; }
|
||||||
public bool IsElectric { get; set; } = false;
|
public bool IsElectric { get; set; } = false;
|
||||||
public bool UseHours { 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>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
|
public bool HasOdometerAdjustment { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Primarily used for vehicles with odometer units different from user's settings.
|
||||||
|
/// </summary>
|
||||||
|
public string OdometerMultiplier { get; set; } = "1";
|
||||||
|
/// <summary>
|
||||||
|
/// Primarily used for vehicles where the odometer does not reflect actual mileage.
|
||||||
|
/// </summary>
|
||||||
|
public string OdometerDifference { get; set; } = "0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
//Print Messages
|
||||||
|
StaticHelper.InitMessage(builder.Configuration);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
|
|
||||||
@@ -66,6 +69,7 @@ builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
|
|||||||
//configure logic
|
//configure logic
|
||||||
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
|
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
|
||||||
builder.Services.AddSingleton<IUserLogic, UserLogic>();
|
builder.Services.AddSingleton<IUserLogic, UserLogic>();
|
||||||
|
builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>();
|
||||||
|
|
||||||
if (!Directory.Exists("data"))
|
if (!Directory.Exists("data"))
|
||||||
{
|
{
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -1,12 +1,8 @@
|
|||||||

|

|
||||||
|
|
||||||
A self-hosted, open-source vehicle service records and maintenance tracker.
|
Self-Hosted, Open-Source, Web-Based Vehicle Maintenance and Fuel Mileage Tracker
|
||||||
|
|
||||||
Visit our website: https://lubelogger.com
|
Website: https://lubelogger.com
|
||||||
|
|
||||||
Support this project by [Subscribing on Patreon](https://patreon.com/LubeLogger) or [Making a Donation](https://buy.stripe.com/aEU9Egc8DdMc9bO144)
|
|
||||||
|
|
||||||
Note: Commercial users are required to maintain an active Patreon subscripton to be compliant with our licensing model.
|
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
Because nobody should have to deal with a homemade spreadsheet or a shoebox full of receipts when it comes to vehicle maintenance.
|
Because nobody should have to deal with a homemade spreadsheet or a shoebox full of receipts when it comes to vehicle maintenance.
|
||||||
@@ -19,14 +15,14 @@ Because nobody should have to deal with a homemade spreadsheet or a shoebox full
|
|||||||
## Demo
|
## Demo
|
||||||
Try it out before you download it! The live demo resets every 20 minutes.
|
Try it out before you download it! The live demo resets every 20 minutes.
|
||||||
|
|
||||||
[Live Demo - Latest Stable](https://demo.lubelogger.com) Login using username "test" and password "1234"
|
[Live Demo](https://demo.lubelogger.com) Login using username "test" and password "1234"
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
LubeLogger is distributed both as a Docker image or a Windows Standalone Executable.
|
LubeLogger is available as both a Docker Image and a Windows Standalone Executable.
|
||||||
|
|
||||||
Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started) on how to download either of them
|
Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started) on how to download either of them
|
||||||
|
|
||||||
### Docker Setup (Manual Build)
|
### Docker Setup (Manual Build for Advanced Users)
|
||||||
1. Install Docker
|
1. Install Docker
|
||||||
2. Clone this repo
|
2. Clone this repo
|
||||||
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
|
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
|
||||||
@@ -35,6 +31,13 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started)
|
|||||||
6. If using traefik, use docker-compose.traefik.yml
|
6. If using traefik, use docker-compose.traefik.yml
|
||||||
7. Run `docker-compose up`
|
7. Run `docker-compose up`
|
||||||
|
|
||||||
|
### Need Help?
|
||||||
|
[Documentation](https://docs.lubelogger.com/)
|
||||||
|
|
||||||
|
[Troubleshooting Guide](https://docs.lubelogger.com/Troubleshooting)
|
||||||
|
|
||||||
|
[Search Existing Issues](https://github.com/hargata/lubelog/issues)
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- Bootstrap
|
- Bootstrap
|
||||||
- LiteDB
|
- LiteDB
|
||||||
@@ -44,3 +47,11 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started)
|
|||||||
- CsvHelper
|
- CsvHelper
|
||||||
- Chart.js
|
- Chart.js
|
||||||
- Drawdown
|
- Drawdown
|
||||||
|
|
||||||
|
## License
|
||||||
|
LubeLogger utilizes a dual-licensing model, see [License](/LICENSE) for more information
|
||||||
|
|
||||||
|
## Support
|
||||||
|
Support this project by [Subscribing on Patreon](https://patreon.com/LubeLogger) or [Making a Donation](https://buy.stripe.com/aEU9Egc8DdMc9bO144)
|
||||||
|
|
||||||
|
Note: Commercial users are required to maintain an active Patreon subscripton to be compliant with our licensing model.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<h6>Method</h6>
|
<h6>Method</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<h6>Endpoint</h6>
|
<h6>Endpoint</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicles</code>
|
<code>/api/vehicles</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/servicerecords</code>
|
<code>/api/vehicle/servicerecords</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/servicerecords/add</code>
|
<code>/api/vehicle/servicerecords/add</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/repairrecords</code>
|
<code>/api/vehicle/repairrecords</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/repairrecords/add</code>
|
<code>/api/vehicle/repairrecords/add</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/upgraderecords</code>
|
<code>/api/vehicle/upgraderecords</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/upgraderecords/add</code>
|
<code>/api/vehicle/upgraderecords/add</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/taxrecords</code>
|
<code>/api/vehicle/taxrecords</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/taxrecords/add</code>
|
<code>/api/vehicle/taxrecords/add</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/gasrecords</code>
|
<code>/api/vehicle/gasrecords</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/gasrecords/add</code>
|
<code>/api/vehicle/gasrecords/add</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -229,7 +229,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/reminders</code>
|
<code>/api/vehicle/reminders</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/reminders/send</code>
|
<code>/api/vehicle/reminders/send</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/makebackup</code>
|
<code>/api/makebackup</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/odometerrecords</code>
|
<code>/api/vehicle/odometerrecords</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -285,11 +285,25 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</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="row">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
<div class="col-5">
|
<div class="col-5 copyable">
|
||||||
<code>/api/vehicle/odometerrecords/add</code>
|
<code>/api/vehicle/odometerrecords/add</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@@ -300,8 +314,14 @@
|
|||||||
<br />
|
<br />
|
||||||
Body(form-data): {<br />
|
Body(form-data): {<br />
|
||||||
date - Date to be entered<br />
|
date - Date to be entered<br />
|
||||||
|
initialOdometer - Initial Odometer reading(optional)<br />
|
||||||
odometer - Odometer reading<br />
|
odometer - Odometer reading<br />
|
||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
$('.copyable').on('click', function (e) {
|
||||||
|
copyToClipboard(e.currentTarget);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -15,11 +15,11 @@
|
|||||||
}
|
}
|
||||||
@model AdminViewModel
|
@model AdminViewModel
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row d-flex align-items-center justify-content-between justify-content-md-start">
|
||||||
<div class="col-1">
|
<div class="col-2 col-md-1">
|
||||||
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
|
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-11">
|
<div class="col-6 col-md-7 text-end text-md-start">
|
||||||
<span class="display-6">@translator.Translate(userLanguage, "Admin Panel")</span>
|
<span class="display-6">@translator.Translate(userLanguage, "Admin Panel")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,11 +120,6 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function copyToClipboard(e) {
|
|
||||||
var textToCopy = e.textContent;
|
|
||||||
navigator.clipboard.writeText(textToCopy);
|
|
||||||
successToast("Copied to Clipboard");
|
|
||||||
}
|
|
||||||
function generateNewToken() {
|
function generateNewToken() {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Generate Token',
|
title: 'Generate Token',
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
ViewData["Title"] = "LubeLogger";
|
ViewData["Title"] = "LubeLogger";
|
||||||
}
|
}
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/garage.js"></script>
|
<script src="~/js/garage.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/supplyrecord.js"></script>
|
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/lib/drawdown/drawdown.js"></script>
|
<script src="~/lib/drawdown/drawdown.js"></script>
|
||||||
}
|
}
|
||||||
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
||||||
@@ -41,6 +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>
|
<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>
|
</li>
|
||||||
}
|
}
|
||||||
|
@if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
<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>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" onclick="performLogOut()"><span class="display-3 ms-2"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</span></button>
|
<button class="nav-link" onclick="performLogOut()"><span class="display-3 ms-2"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</span></button>
|
||||||
</li>
|
</li>
|
||||||
@@ -84,6 +90,12 @@
|
|||||||
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</a>
|
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
@if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item" onclick="showAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
<li>
|
<li>
|
||||||
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</button>
|
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -107,7 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="addVehicleModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="addVehicleModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="addVehicleModalContent">
|
<div class="modal-content" id="addVehicleModalContent">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,6 +130,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="accountInformationModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content" id="accountInformationModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
loadGarage();
|
loadGarage();
|
||||||
bindWindowResize();
|
bindWindowResize();
|
||||||
|
|||||||
35
Views/Home/_AccountModal.cshtml
Normal file
35
Views/Home/_AccountModal.cshtml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@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="addVehicleModalLabel">@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="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
|
||||||
|
<input type="text" id="inputEmail" class="form-control" placeholder="@translator.Translate(userLanguage, "Email Address")" value="@Model.EmailAddress">
|
||||||
|
<label for="inputPassword">@translator.Translate(userLanguage, "New Password")</label>
|
||||||
|
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "New Password")" value="">
|
||||||
|
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||||
|
<input type="text" id="inputToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Token")" value="">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<a onclick="generateTokenForUser()" class="btn btn-link">@translator.Translate(userLanguage, "Send Token")</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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="validateAndSaveUserAccount()" class="btn btn-primary">@translator.Translate(userLanguage, "Update")</button>
|
||||||
|
</div>
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
<!option @(Model.Id == (int)ImportMode.SupplyRecord ? "selected" : "") value="@((int)ImportMode.SupplyRecord)">@translator.Translate(userLanguage, "Supplies")</!option>
|
<!option @(Model.Id == (int)ImportMode.SupplyRecord ? "selected" : "") value="@((int)ImportMode.SupplyRecord)">@translator.Translate(userLanguage, "Supplies")</!option>
|
||||||
<!option @(Model.Id == (int)ImportMode.PlanRecord ? "selected" : "") value="@((int)ImportMode.PlanRecord)">@translator.Translate(userLanguage, "Planner")</!option>
|
<!option @(Model.Id == (int)ImportMode.PlanRecord ? "selected" : "") value="@((int)ImportMode.PlanRecord)">@translator.Translate(userLanguage, "Planner")</!option>
|
||||||
<!option @(Model.Id == (int)ImportMode.OdometerRecord ? "selected" : "") value="@((int)ImportMode.OdometerRecord)">@translator.Translate(userLanguage, "Odometer")</!option>
|
<!option @(Model.Id == (int)ImportMode.OdometerRecord ? "selected" : "") value="@((int)ImportMode.OdometerRecord)">@translator.Translate(userLanguage, "Odometer")</!option>
|
||||||
|
<!option @(Model.Id == (int)ImportMode.VehicleRecord ? "selected" : "") value="@((int)ImportMode.VehicleRecord)">@translator.Translate(userLanguage, "Vehicle")</!option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
@model List<Vehicle>
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@model List<Vehicle>
|
||||||
@{
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||||
}
|
}
|
||||||
@if (recordTags.Any())
|
@if (recordTags.Any())
|
||||||
@@ -23,17 +28,24 @@
|
|||||||
<div class="row gy-3 align-items-stretch vehiclesContainer">
|
<div class="row gy-3 align-items-stretch vehiclesContainer">
|
||||||
@foreach (Vehicle vehicle in Model)
|
@foreach (Vehicle vehicle in Model)
|
||||||
{
|
{
|
||||||
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
|
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
|
||||||
<div class="card" onclick="viewVehicle(@vehicle.Id)">
|
{
|
||||||
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none;" />
|
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
|
||||||
<div class="card-body">
|
<div class="card" onclick="viewVehicle(@vehicle.Id)">
|
||||||
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>
|
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
|
||||||
<h5 class="card-title text-truncate">@($"{vehicle.Make}")</h5>
|
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
|
||||||
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5>
|
{
|
||||||
<p class="card-text text-truncate">@vehicle.LicensePlate</p>
|
<div class="vehicle-sold-banner"><p class='display-6 mb-0'>@translator.Translate(userLanguage, "SOLD")</p></div>
|
||||||
|
}
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>
|
||||||
|
<h5 class="card-title text-truncate">@($"{vehicle.Make}")</h5>
|
||||||
|
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5>
|
||||||
|
<p class="card-text text-truncate">@vehicle.LicensePlate</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
}
|
||||||
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 garage-item-add">
|
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 garage-item-add">
|
||||||
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
||||||
@@ -41,4 +53,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,6 +53,14 @@
|
|||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoOdometerInsert" checked="@Model.UserConfig.EnableAutoOdometerInsert">
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoOdometerInsert" checked="@Model.UserConfig.EnableAutoOdometerInsert">
|
||||||
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")</small></label>
|
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")</small></label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableExtraFieldColumns" checked="@Model.UserConfig.EnableExtraFieldColumns">
|
||||||
|
<label class="form-check-label" for="enableExtraFieldColumns">@translator.Translate(userLanguage, "Show Extra Field Columns")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Enabling this may cause performance issues")</small></label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideSoldVehicles" checked="@Model.UserConfig.HideSoldVehicles">
|
||||||
|
<label class="form-check-label" for="hideSoldVehicles">@translator.Translate(userLanguage, "Hide Sold Vehicles")</label>
|
||||||
|
</div>
|
||||||
<div class="form-check form-switch @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
|
<div class="form-check form-switch @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
|
||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
|
||||||
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
|
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
|
||||||
@@ -180,10 +188,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Manage Extra Fields")</span>
|
<span class="lead">@translator.Translate(userLanguage, "Server-wide Settings")</span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-grid">
|
<div class="col-6 d-grid">
|
||||||
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Add/Remove Extra Fields")</button>
|
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Extra Fields")</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 d-grid">
|
||||||
|
<button onclick="showReminderUrgencyThresholdModal()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Reminders")</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -201,7 +212,7 @@
|
|||||||
<img src="/defaults/lubelogger_logo.png" />
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<small class="text-body-secondary">Version 1.2.2</small>
|
<small class="text-body-secondary">@($"{translator.Translate(userLanguage, "Version")} {StaticHelper.VersionNumber}")</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Proudly developed in the rural town of Price, Utah by Hargata Softworks.
|
Proudly developed in the rural town of Price, Utah by Hargata Softworks.
|
||||||
@@ -246,6 +257,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
function showReminderUrgencyThresholdModal(){
|
||||||
|
Swal.fire({
|
||||||
|
title: decodeHTMLEntities('@translator.Translate(userLanguage, "Configure Reminder Urgency Thresholds")'),
|
||||||
|
html: `
|
||||||
|
<form>
|
||||||
|
<div class='d-flex flex-row align-items-center'>
|
||||||
|
<label class='form-label me-auto'>${decodeHTMLEntities('@translator.Translate(userLanguage, "Urgent(Days)")')}</label>
|
||||||
|
<input type="text" id="inputUrgentDays" class="form-control" style='width:40%' placeholder="${decodeHTMLEntities('@translator.Translate(userLanguage, "Urgent")')}" value='@Model.UserConfig.ReminderUrgencyConfig.UrgentDays'>
|
||||||
|
</div>
|
||||||
|
<div class='d-flex flex-row align-items-center mt-2'>
|
||||||
|
<label class='form-label me-auto'>${decodeHTMLEntities('@translator.Translate(userLanguage, "Very Urgent(Days)")')}</label>
|
||||||
|
<input type="text" id="inputVeryUrgentDays" class="form-control" style='width:40%' placeholder="${decodeHTMLEntities('@translator.Translate(userLanguage, "Very Urgent")')}" value='@Model.UserConfig.ReminderUrgencyConfig.VeryUrgentDays'>
|
||||||
|
</div>
|
||||||
|
<div class='d-flex flex-row align-items-center mt-2'>
|
||||||
|
<label class='form-label me-auto'>${decodeHTMLEntities('@translator.Translate(userLanguage, "Urgent(Distance)")')}</label>
|
||||||
|
<input type="text" id="inputUrgentDistance" class="form-control" style='width:40%' placeholder="${decodeHTMLEntities('@translator.Translate(userLanguage, "Urgent")')}" value='@Model.UserConfig.ReminderUrgencyConfig.UrgentDistance'>
|
||||||
|
</div>
|
||||||
|
<div class='d-flex flex-row align-items-center mt-2'>
|
||||||
|
<label class='form-label me-auto'>${decodeHTMLEntities('@translator.Translate(userLanguage, "Very Urgent(Distance)")')}</label>
|
||||||
|
<input type="text" id="inputVeryUrgentDistance" class="form-control" style='width:40%' placeholder="${decodeHTMLEntities('@translator.Translate(userLanguage, "Very Urgent")')}" value='@Model.UserConfig.ReminderUrgencyConfig.VeryUrgentDistance'>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
|
confirmButtonText: decodeHTMLEntities('@translator.Translate(userLanguage, "Save")'),
|
||||||
|
focusConfirm: false,
|
||||||
|
preConfirm: () => {
|
||||||
|
const urgentDays = $("#inputUrgentDays").val();
|
||||||
|
const veryUrgentDays = $("#inputVeryUrgentDays").val();
|
||||||
|
const urgentDistance = $("#inputUrgentDistance").val();
|
||||||
|
const veryUrgentDistance = $("#inputVeryUrgentDistance").val();
|
||||||
|
if (!urgentDays || isNaN(urgentDays) || !veryUrgentDays || isNaN(veryUrgentDays) || !urgentDistance || isNaN(urgentDistance) || !veryUrgentDistance || isNaN(veryUrgentDistance)) {
|
||||||
|
Swal.showValidationMessage(`Invalid parameters`)
|
||||||
|
}
|
||||||
|
return { urgentDays, veryUrgentDays, urgentDistance, veryUrgentDistance }
|
||||||
|
},
|
||||||
|
}).then(function (result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
$.post('/Home/SaveReminderUrgencyThreshold', { urgentDays: result.value.urgentDays, veryUrgentDays: result.value.veryUrgentDays, urgentDistance: result.value.urgentDistance, veryUrgentDistance: result.value.veryUrgentDistance }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
setTimeout(function () { window.location.href = '/Home/Index?tab=settings' }, 500);
|
||||||
|
} else {
|
||||||
|
errorToast(genericErrorMessage());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
function showExtraFieldModal() {
|
function showExtraFieldModal() {
|
||||||
$.get(`/Home/GetExtraFieldsModal?importMode=0`, function (data) {
|
$.get(`/Home/GetExtraFieldsModal?importMode=0`, function (data) {
|
||||||
$("#extraFieldModalContent").html(data);
|
$("#extraFieldModalContent").html(data);
|
||||||
@@ -287,6 +345,8 @@
|
|||||||
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
|
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
|
||||||
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
|
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
|
||||||
enableShopSupplies: $("#enableShopSupplies").is(":checked"),
|
enableShopSupplies: $("#enableShopSupplies").is(":checked"),
|
||||||
|
enableExtraFieldColumns: $("#enableExtraFieldColumns").is(":checked"),
|
||||||
|
hideSoldVehicles: $("#hideSoldVehicles").is(":checked"),
|
||||||
preferredGasUnit: $("#preferredGasUnit").val(),
|
preferredGasUnit: $("#preferredGasUnit").val(),
|
||||||
preferredGasMileageUnit: $("#preferredFuelMileageUnit").val(),
|
preferredGasMileageUnit: $("#preferredFuelMileageUnit").val(),
|
||||||
userLanguage: $("#defaultLanguage").val(),
|
userLanguage: $("#defaultLanguage").val(),
|
||||||
@@ -342,6 +402,7 @@
|
|||||||
function restoreBackup(event) {
|
function restoreBackup(event) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("file", event.files[0]);
|
formData.append("file", event.files[0]);
|
||||||
|
console.log('LubeLogger - DB Restoration Started');
|
||||||
sloader.show();
|
sloader.show();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/Files/HandleFileUpload",
|
url: "/Files/HandleFileUpload",
|
||||||
@@ -355,16 +416,21 @@
|
|||||||
$.post('/Files/RestoreBackup', { fileName: response }, function (data) {
|
$.post('/Files/RestoreBackup', { fileName: response }, function (data) {
|
||||||
sloader.hide();
|
sloader.hide();
|
||||||
if (data) {
|
if (data) {
|
||||||
|
console.log('LubeLogger - DB Restoration Completed');
|
||||||
successToast("Backup Restored");
|
successToast("Backup Restored");
|
||||||
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
|
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
|
||||||
} else {
|
} else {
|
||||||
errorToast(genericErrorMessage());
|
errorToast(genericErrorMessage());
|
||||||
|
console.log('LubeLogger - DB Restoration Failed - Failed to process backup file.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log('LubeLogger - DB Restoration Failed - Failed to upload backup file.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function () {
|
error: function () {
|
||||||
sloader.hide();
|
sloader.hide();
|
||||||
|
console.log('LubeLogger - DB Restoration Failed - Request failed to reach backend, please check file size.');
|
||||||
errorToast("An error has occurred, please check the file size and try again later.");
|
errorToast("An error has occurred, please check the file size and try again later.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
10
Views/Home/_VehicleExtraFields.cshtml
Normal file
10
Views/Home/_VehicleExtraFields.cshtml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@model List<ExtraField>
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<ul class='list-group list-group-flush'>
|
||||||
|
@foreach (ExtraField field in Model)
|
||||||
|
{
|
||||||
|
<li><b>@field.Name</b> : @field.Value</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
ViewData["Title"] = "LubeLogger - Login";
|
ViewData["Title"] = "LubeLogger - Login";
|
||||||
}
|
}
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/login.js"></script>
|
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
}
|
}
|
||||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
ViewData["Title"] = "LubeLogger - Login";
|
ViewData["Title"] = "LubeLogger - Login";
|
||||||
}
|
}
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/login.js"></script>
|
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
}
|
}
|
||||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -38,6 +38,11 @@
|
|||||||
<button type="button" class="btn btn-secondary mt-2" onclick="remoteLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>@($"{translator.Translate(userLanguage, "Login via")} {openIdConfigName}")</button>
|
<button type="button" class="btn btn-secondary mt-2" onclick="remoteLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>@($"{translator.Translate(userLanguage, "Login via")} {openIdConfigName}")</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="form-group">
|
||||||
|
<small class="text-body-secondary">@config.GetMOTD()</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<a href="/Login/ForgotPassword" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Forgot Password")</a>
|
<a href="/Login/ForgotPassword" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Forgot Password")</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
ViewData["Title"] = "LubeLogger - Register";
|
ViewData["Title"] = "LubeLogger - Register";
|
||||||
}
|
}
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/login.js"></script>
|
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
}
|
}
|
||||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
ViewData["Title"] = "LubeLogger - Register";
|
ViewData["Title"] = "LubeLogger - Register";
|
||||||
}
|
}
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/login.js"></script>
|
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
}
|
}
|
||||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
ViewData["Title"] = "LubeLogger - Register";
|
ViewData["Title"] = "LubeLogger - Register";
|
||||||
}
|
}
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/login.js"></script>
|
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
}
|
}
|
||||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -45,12 +45,12 @@
|
|||||||
<link rel="apple-touch-startup-image" href="~/defaults/lubelogger_launch.png" />
|
<link rel="apple-touch-startup-image" href="~/defaults/lubelogger_launch.png" />
|
||||||
<link rel="manifest" href="~/manifest.json">
|
<link rel="manifest" href="~/manifest.json">
|
||||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||||
<script src="~/js/shared.js"></script>
|
<script src="~/js/shared.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="~/lib/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
|
<script src="~/lib/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
|
||||||
<script src="~/lib/bootstrap-tagsinput/bootstrap-tagsinput.js"></script>
|
<script src="~/lib/bootstrap-tagsinput/bootstrap-tagsinput.js"></script>
|
||||||
<script src="~/sweetalert/sweetalert2.all.min.js"></script>
|
<script src="~/sweetalert/sweetalert2.all.min.js"></script>
|
||||||
<script src="~/js/loader.js"></script>
|
<script src="~/js/loader.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script>
|
<script>
|
||||||
function getGlobalConfig() {
|
function getGlobalConfig() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -10,18 +10,18 @@
|
|||||||
}
|
}
|
||||||
@model Vehicle
|
@model Vehicle
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/vehicle.js"></script>
|
<script src="~/js/vehicle.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/servicerecord.js"></script>
|
<script src="~/js/servicerecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/gasrecord.js"></script>
|
<script src="~/js/gasrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/collisionrecord.js"></script>
|
<script src="~/js/collisionrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/taxrecord.js"></script>
|
<script src="~/js/taxrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/reminderrecord.js"></script>
|
<script src="~/js/reminderrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/upgraderecord.js"></script>
|
<script src="~/js/upgraderecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/note.js"></script>
|
<script src="~/js/note.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/reports.js"></script>
|
<script src="~/js/reports.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/supplyrecord.js"></script>
|
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/planrecord.js"></script>
|
<script src="~/js/planrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/js/odometerrecord.js"></script>
|
<script src="~/js/odometerrecord.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
<script src="~/lib/chart-js/chart.umd.js"></script>
|
<script src="~/lib/chart-js/chart.umd.js"></script>
|
||||||
<script src="~/lib/drawdown/drawdown.js"></script>
|
<script src="~/lib/drawdown/drawdown.js"></script>
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="editVehicleModalContent">
|
<div class="modal-content" id="editVehicleModalContent">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,7 +169,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function GetVehicleId() {
|
function GetVehicleId() {
|
||||||
return { vehicleId: @Model.Id};
|
return {
|
||||||
|
vehicleId: @Model.Id,
|
||||||
|
hasOdometerAdjustment: @Model.HasOdometerAdjustment.ToString().ToLower(),
|
||||||
|
odometerDifference: decodeHTMLEntities('@Model.OdometerDifference'),
|
||||||
|
odometerMultiplier: decodeHTMLEntities('@Model.OdometerMultiplier')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
function GetDefaultTab() {
|
function GetDefaultTab() {
|
||||||
return { tab: "@userConfig.DefaultTab" };
|
return { tab: "@userConfig.DefaultTab" };
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
<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>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
var hideZero = userConfig.HideZero;
|
var hideZero = userConfig.HideZero;
|
||||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||||
var userLanguage = userConfig.UserLanguage;
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
if (userConfig.EnableExtraFieldColumns)
|
||||||
|
{
|
||||||
|
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
|
||||||
|
}
|
||||||
|
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.RepairRecord);
|
||||||
}
|
}
|
||||||
@model List<CollisionRecord>
|
@model List<CollisionRecord>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -38,6 +44,49 @@
|
|||||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="searchTableRows('accident-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Description" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="@elementId">
|
||||||
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -58,22 +107,30 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage,"Date")</th>
|
<th scope="col" class="col-2 col-xl-1 flex-grow-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
|
||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage,"Odometer")</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 col-xl-4">@translator.Translate(userLanguage,"Description")</th>
|
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||||
<th scope="col" class="col-2" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage,"Cost")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||||
<th scope="col" class="col-3">@translator.Translate(userLanguage,"Notes")</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)
|
||||||
|
{
|
||||||
|
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (CollisionRecord collisionRecord in Model)
|
@foreach (CollisionRecord collisionRecord in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@collisionRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditCollisionRecordModal,@collisionRecord.Id)" data-tags='@string.Join(" ", collisionRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@collisionRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditCollisionRecordModal,@collisionRecord.Id)" data-tags='@string.Join(" ", collisionRecord.Tags)'>
|
||||||
<td class="col-2 col-xl-1">@collisionRecord.Date.ToShortDateString()</td>
|
<td class="col-2 col-xl-1 flex-grow-1" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(collisionRecord.Date)">@collisionRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-2">@collisionRecord.Mileage</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@collisionRecord.Mileage</td>
|
||||||
<td class="col-3 col-xl-4">@collisionRecord.Description</td>
|
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@collisionRecord.Description</td>
|
||||||
<td class="col-2" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(collisionRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -100,4 +157,12 @@
|
|||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||||
</ul>
|
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
|
||||||
|
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||||
|
</ul>
|
||||||
|
@if (userColumnPreferences.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
|
||||||
|
}
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
{
|
{
|
||||||
consumptionUnit = "imp gal";
|
consumptionUnit = "imp gal";
|
||||||
fuelEconomyUnit = useHours ? "h/g" : "mpg";
|
fuelEconomyUnit = useHours ? "h/g" : "mpg";
|
||||||
} else if (useUKMPG)
|
}
|
||||||
|
else if (useUKMPG)
|
||||||
{
|
{
|
||||||
fuelEconomyUnit = useHours ? "l/100h" : "l/100mi.";
|
fuelEconomyUnit = useHours ? "l/100h" : "l/100mi.";
|
||||||
consumptionUnit = "l";
|
consumptionUnit = "l";
|
||||||
@@ -40,26 +41,33 @@
|
|||||||
consumptionUnit = useMPG ? "US gal" : "l";
|
consumptionUnit = useMPG ? "US gal" : "l";
|
||||||
fuelEconomyUnit = useHours ? (useMPG ? "h/g" : "l/100h") : (useMPG ? "mpg" : "l/100km");
|
fuelEconomyUnit = useHours ? (useMPG ? "h/g" : "l/100h") : (useMPG ? "mpg" : "l/100km");
|
||||||
}
|
}
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
if (userConfig.EnableExtraFieldColumns)
|
||||||
|
{
|
||||||
|
extraFields = Model.GasRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
|
||||||
|
}
|
||||||
|
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.GasRecord);
|
||||||
}
|
}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="d-flex align-items-center flex-wrap">
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Gas Records")}: {Model.GasRecords.Count()}")</span>
|
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Gas Records")}: {Model.GasRecords.Count()}")</span>
|
||||||
@if (Model.GasRecords.Where(x => x.MilesPerGallon > 0).Any())
|
@if (Model.GasRecords.Where(x => x.MilesPerGallon > 0).Any())
|
||||||
{
|
{
|
||||||
<span class="ms-2 badge bg-primary" id="averageFuelMileageLabel">@($"{translator.Translate(userLanguage,"Average Fuel Economy")}: {gasHelper.GetAverageGasMileage(Model.GasRecords, useMPG)}")</span>
|
<span class="ms-2 badge bg-primary" id="averageFuelMileageLabel">@($"{translator.Translate(userLanguage, "Average Fuel Economy")}: {gasHelper.GetAverageGasMileage(Model.GasRecords, useMPG)}")</span>
|
||||||
if (useMPG)
|
if (useMPG)
|
||||||
{
|
{
|
||||||
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage,"Min Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage, "Min Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage,"Max Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage, "Max Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage,"Min Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage, "Min Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage,"Max Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage, "Max Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage,"Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage, "Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
||||||
<span class="ms-2 badge bg-success" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total Cost")}: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
|
<span class="ms-2 badge bg-success" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total Cost")}: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
|
||||||
@foreach (string recordTag in recordTags)
|
@foreach (string recordTag in recordTags)
|
||||||
{
|
{
|
||||||
<span onclick="toggleGasFilter(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
<span onclick="toggleGasFilter(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||||
@@ -74,19 +82,82 @@
|
|||||||
@if (enableCsvImports)
|
@if (enableCsvImports)
|
||||||
{
|
{
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Gas Record")</button>
|
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Gas Record")</button>
|
||||||
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="searchGasTableRows()">@translator.Translate(userLanguage, "Search")</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='daterefueled' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_DateRefueled" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_DateRefueled">@translator.Translate(userLanguage, "Date Refueled")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='delta' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Delta" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Delta">@translator.Translate(userLanguage, "Delta")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='consumption' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Consumption" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Consumption">@translator.Translate(userLanguage, "Consumption")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='fueleconomy' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_FuelEconomy" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_FuelEconomy">@translator.Translate(userLanguage, "Fuel Economy")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='unitcost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_UnitCost" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Notes">
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="@elementId">
|
||||||
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
} else {
|
}
|
||||||
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Gas Record")</button>
|
else
|
||||||
|
{
|
||||||
|
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Gas Record")</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,26 +171,36 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage,"Date Refueled")</th>
|
<th scope="col" class="col-2 flex-grow-1" data-column="daterefueled">@translator.Translate(userLanguage, "Date Refueled")</th>
|
||||||
<th scope="col" class="col-2">@($"{translator.Translate(userLanguage, "Odometer")}({distanceUnit})")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@($"{translator.Translate(userLanguage, "Odometer")}({distanceUnit})")</th>
|
||||||
<th scope="col" class="col-1" style="cursor:pointer;" onclick="toggleSort('gas-tab-pane', this)">@($"Δ({distanceUnit})")</th>
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1" data-column="delta" style="cursor:pointer;" onclick="toggleSort('gas-tab-pane', this)">@($"Δ({distanceUnit})")</th>
|
||||||
<th scope="col" class="col-2" data-gas="consumption" data-unit="@consumptionUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{translator.Translate(userLanguage,"Consumption")}({consumptionUnit})")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="consumption" data-gas="consumption" data-unit="@consumptionUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{translator.Translate(userLanguage, "Consumption")}({consumptionUnit})")</th>
|
||||||
<th scope="col" class="col-3" data-gas="fueleconomy" data-unit="@fuelEconomyUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{@translator.Translate(userLanguage,"Fuel Economy")}({fuelEconomyUnit})")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="fueleconomy" data-gas="fueleconomy" data-unit="@fuelEconomyUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{@translator.Translate(userLanguage, "Fuel Economy")}({fuelEconomyUnit})")</th>
|
||||||
<th scope="col" class="col-1" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage,"Cost")</th>
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||||
<th scope="col" class="col-1" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage,"Unit Cost")</th>
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1" data-column="unitcost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Unit Cost")</th>
|
||||||
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (GasRecordViewModel gasRecord in Model.GasRecords)
|
@foreach (GasRecordViewModel gasRecord in Model.GasRecords)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@gasRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditGasRecordModal,@gasRecord.Id)" data-tags='@string.Join(" ", gasRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@gasRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditGasRecordModal,@gasRecord.Id)" data-tags='@string.Join(" ", gasRecord.Tags)'>
|
||||||
<td class="col-2">@gasRecord.Date</td>
|
<td class="col-2 flex-grow-1" data-column="daterefueled">@gasRecord.Date</td>
|
||||||
<td class="col-2" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@gasRecord.Mileage</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@gasRecord.Mileage</td>
|
||||||
<td class="col-1">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="delta">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
|
||||||
<td class="col-2" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString("F")</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString("F")</td>
|
||||||
<td class="col-3" data-gas-type="fueleconomy" data-aggregated='@(gasRecord.IncludeInAverage.ToString().ToLower())'>@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1" data-column="fueleconomy" data-gas-type="fueleconomy" data-aggregated='@(gasRecord.IncludeInAverage.ToString().ToLower())'>@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
||||||
<td class="col-1" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
|
||||||
<td class="col-1" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="unitcost" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
||||||
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</td>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(gasRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -131,7 +212,6 @@
|
|||||||
<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">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="gasRecordModalContent">
|
<div class="modal-content" id="gasRecordModalContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,18 +219,24 @@
|
|||||||
<ul class="table-context-menu dropdown-menu" style="display:none;">
|
<ul class="table-context-menu dropdown-menu" style="display:none;">
|
||||||
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
|
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
|
||||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
||||||
|
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
|
||||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||||
|
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@if (userColumnPreferences.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
|
||||||
|
}
|
||||||
<script>
|
<script>
|
||||||
@if (!string.IsNullOrWhiteSpace(preferredFuelEconomyUnit))
|
@if (!string.IsNullOrWhiteSpace(preferredFuelEconomyUnit))
|
||||||
{
|
{
|
||||||
@:convertFuelMileageUnits(decodeHTMLEntities('@fuelEconomyUnit'), decodeHTMLEntities('@preferredFuelEconomyUnit'), false);
|
@:convertFuelMileageUnits(decodeHTMLEntities('@fuelEconomyUnit'), decodeHTMLEntities('@preferredFuelEconomyUnit'), false);
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(preferredGasUnit))
|
@if (!string.IsNullOrWhiteSpace(preferredGasUnit))
|
||||||
{
|
{
|
||||||
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
|
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,14 +103,14 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.GasRecord.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.GasRecord.Files)
|
||||||
<label for="gasRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
<label for="gasRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<label for="gasRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
<label for="gasRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
50
Views/Vehicle/_GasRecordsModal.cshtml
Normal file
50
Views/Vehicle/_GasRecordsModal.cshtml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@model GasRecordEditModel
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">@translator.Translate(userLanguage,"Edit Multiple Gas Records")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideAddGasRecordModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="gasRecordDate">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="gasRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
|
</div>
|
||||||
|
<label for="gasRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
|
<input type="number" inputmode="numeric" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<label for="gasRecordConsumption">@translator.Translate(userLanguage, "Fuel Consumption")</label>
|
||||||
|
<input type="text" inputmode="decimal" id="gasRecordConsumption" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<label for="gasRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
|
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<label for="gasRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||||
|
<select multiple class="form-select" id="gasRecordTag"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="gasRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
<textarea id="gasRecordNotes" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideAddGasRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveMultipleGasRecordsToVehicle()">@translator.Translate(userLanguage, "Edit")</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var recordsToEdit = [];
|
||||||
|
@foreach(int recordId in Model.RecordIds)
|
||||||
|
{
|
||||||
|
@:recordsToEdit.push(@recordId);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -34,14 +34,14 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
|
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="noteFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="noteFiles">
|
||||||
<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>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="noteFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="noteFiles">
|
||||||
<br />
|
<br />
|
||||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
@foreach (Note note in Model)
|
@foreach (Note note in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@note.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditNoteModal,@note.Id)" data-tags='@string.Join(" ", note.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@note.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditNoteModal,@note.Id)" data-tags='@string.Join(" ", note.Tags)'>
|
||||||
@if (note.Pinned)
|
@if (note.Pinned)
|
||||||
{
|
{
|
||||||
<td class="col-3"><i class='bi bi-pin-fill me-2'></i>@note.Description</td>
|
<td class="col-3"><i class='bi bi-pin-fill me-2'></i>@note.Description</td>
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
<input type="text" id="odometerRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date recorded")" value="@Model.Date">
|
<input type="text" id="odometerRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date recorded")" value="@Model.Date">
|
||||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
</div>
|
</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)">
|
||||||
<label for="odometerRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
<label for="odometerRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||||
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
|
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||||
<label for="odometerRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
<label for="odometerRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||||
@@ -48,14 +50,14 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="odometerRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
<label for="odometerRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<label for="odometerRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
<label for="odometerRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,12 +7,19 @@
|
|||||||
var hideZero = userConfig.HideZero;
|
var hideZero = userConfig.HideZero;
|
||||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||||
var userLanguage = userConfig.UserLanguage;
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
if (userConfig.EnableExtraFieldColumns)
|
||||||
|
{
|
||||||
|
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
|
||||||
|
}
|
||||||
|
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x=>x.Tab == ImportMode.OdometerRecord);
|
||||||
}
|
}
|
||||||
@model List<OdometerRecord>
|
@model List<OdometerRecord>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="d-flex align-items-center flex-wrap">
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Odometer Records")}: {Model.Count()}")</span>
|
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Odometer Records")}: {Model.Count()}")</span>
|
||||||
|
<span class="ms-2 badge bg-primary" data-aggregate-type="sum-distance">@($"{translator.Translate(userLanguage, "Total Distance")}: {Model.Sum(x => x.DistanceTraveled)}")</span>
|
||||||
@foreach (string recordTag in recordTags)
|
@foreach (string recordTag in recordTags)
|
||||||
{
|
{
|
||||||
<span onclick="filterTable('odometer-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
<span onclick="filterTable('odometer-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||||
@@ -37,6 +44,49 @@
|
|||||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('OdometerRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('OdometerRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="searchTableRows('odometer-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='initialodometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_InitialOdometer" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_InitialOdometer">@translator.Translate(userLanguage, "Initial Odometer")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='distance' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Distance" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Distance">@translator.Translate(userLanguage, "Distance")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="@elementId">
|
||||||
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -57,18 +107,30 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage, "Date")</th>
|
<th scope="col" class="col-2 col-xl-1 flex-grow-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
|
||||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Odometer")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="initialodometer">@translator.Translate(userLanguage, "Initial Odometer")</th>
|
||||||
<th scope="col" class="col-7 col-xl-8">@translator.Translate(userLanguage, "Notes")</th>
|
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
|
||||||
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="distance" onclick="toggleSort('odometer-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Distance")</th>
|
||||||
|
<th scope="col" class="col-2 col-xl-3 flex-grow-1 flex-shrink-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (OdometerRecord odometerRecord in Model)
|
@foreach (OdometerRecord odometerRecord in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@odometerRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditOdometerRecordModal,@odometerRecord.Id)" data-tags='@string.Join(" ", odometerRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@odometerRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditOdometerRecordModal,@odometerRecord.Id)" data-tags='@string.Join(" ", odometerRecord.Tags)'>
|
||||||
<td class="col-2 col-xl-1">@odometerRecord.Date.ToShortDateString()</td>
|
<td class="col-2 col-xl-1 flex-grow-1" data-column="date">@odometerRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-3" data-record-type="cost">@odometerRecord.Mileage</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1" data-column="initialodometer">@odometerRecord.InitialMileage</td>
|
||||||
<td class="col-7 col-xl-8 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1" data-column="odometer">@odometerRecord.Mileage</td>
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="distance" data-record-type="distance">@(odometerRecord.DistanceTraveled == default ? "---" : odometerRecord.DistanceTraveled)</td>
|
||||||
|
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(odometerRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -87,7 +149,16 @@
|
|||||||
<ul class="table-context-menu dropdown-menu" style="display:none;">
|
<ul class="table-context-menu dropdown-menu" style="display:none;">
|
||||||
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
|
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
|
||||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
||||||
|
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleOdometerRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
|
||||||
|
<li><hr class="context-menu-multiple context-menu-deselect-all dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="recalculateDistance()">@translator.Translate(userLanguage, "Recalculate Distance")</a></li>
|
||||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||||
</ul>
|
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||||
|
</ul>
|
||||||
|
@if (userColumnPreferences.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
|
||||||
|
}
|
||||||
48
Views/Vehicle/_OdometerRecordsModal.cshtml
Normal file
48
Views/Vehicle/_OdometerRecordsModal.cshtml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@model OdometerRecordEditModel
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">@translator.Translate(userLanguage,"Edit Multiple Odometer Records")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideAddOdometerRecordModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="odometerRecordDate">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="odometerRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<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,"(multiple)")">
|
||||||
|
<label for="odometerRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
|
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||||
|
<label for="odometerRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||||
|
<select multiple class="form-select" id="odometerRecordTag"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="odometerRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
|
||||||
|
<textarea id="odometerRecordNotes" class="form-control" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideAddOdometerRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveMultipleOdometerRecordsToVehicle()">@translator.Translate(userLanguage, "Edit")</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var recordsToEdit = [];
|
||||||
|
@foreach(int recordId in Model.RecordIds)
|
||||||
|
{
|
||||||
|
@:recordsToEdit.push(@recordId);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -65,14 +65,14 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
|
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="planRecordFiles">
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
@foreach (ReminderRecordViewModel reminderRecord in Model)
|
@foreach (ReminderRecordViewModel reminderRecord in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@reminderRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditReminderRecordModal,@reminderRecord.Id)">
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@reminderRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditReminderRecordModal,@reminderRecord.Id)">
|
||||||
@if (reminderRecord.Urgency == ReminderUrgency.VeryUrgent)
|
@if (reminderRecord.Urgency == ReminderUrgency.VeryUrgent)
|
||||||
{
|
{
|
||||||
<td class="col-1"><span class="badge text-bg-danger">@translator.Translate(userLanguage, "Very Urgent")</span></td>
|
<td class="col-1"><span class="badge text-bg-danger">@translator.Translate(userLanguage, "Very Urgent")</span></td>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
<label for="serviceRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
<label for="serviceRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,14 +7,20 @@
|
|||||||
var hideZero = userConfig.HideZero;
|
var hideZero = userConfig.HideZero;
|
||||||
var userLanguage = userConfig.UserLanguage;
|
var userLanguage = userConfig.UserLanguage;
|
||||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
if (userConfig.EnableExtraFieldColumns)
|
||||||
|
{
|
||||||
|
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
|
||||||
|
}
|
||||||
|
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.ServiceRecord);
|
||||||
}
|
}
|
||||||
@model List<ServiceRecord>
|
@model List<ServiceRecord>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="d-flex align-items-center flex-wrap">
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Service Records")}: {Model.Count()}")</span>
|
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Service Records")}: {Model.Count()}")</span>
|
||||||
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||||
@foreach(string recordTag in recordTags)
|
@foreach (string recordTag in recordTags)
|
||||||
{
|
{
|
||||||
<span onclick="filterTable('servicerecord-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
<span onclick="filterTable('servicerecord-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||||
}
|
}
|
||||||
@@ -29,21 +35,64 @@
|
|||||||
@if (enableCsvImports)
|
@if (enableCsvImports)
|
||||||
{
|
{
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Service Record")</button>
|
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Service Record")</button>
|
||||||
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('ServiceRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('ServiceRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('ServiceRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('ServiceRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="searchTableRows('servicerecord-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Description" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="@elementId">
|
||||||
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Service Record")</button>
|
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Service Record")</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,22 +107,30 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage,"Date")</th>
|
<th scope="col" class="col-2 col-xl-1 flex-grow-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
|
||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Odometer")</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 col-xl-4">@translator.Translate(userLanguage, "Description")</th>
|
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||||
<th scope="col" class="col-2" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Notes")</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)
|
||||||
|
{
|
||||||
|
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (ServiceRecord serviceRecord in Model)
|
@foreach (ServiceRecord serviceRecord in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@serviceRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditServiceRecordModal,@serviceRecord.Id)" data-tags='@string.Join(" ", serviceRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@serviceRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditServiceRecordModal,@serviceRecord.Id)" data-tags='@string.Join(" ", serviceRecord.Tags)'>
|
||||||
<td class="col-2 col-xl-1">@serviceRecord.Date.ToShortDateString()</td>
|
<td class="col-2 col-xl-1 flex-grow-1" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(serviceRecord.Date)">@serviceRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-2">@serviceRecord.Mileage</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@serviceRecord.Mileage</td>
|
||||||
<td class="col-3 col-xl-4">@serviceRecord.Description</td>
|
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@serviceRecord.Description</td>
|
||||||
<td class="col-2" data-record-type="cost">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
|
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(serviceRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -85,7 +142,6 @@
|
|||||||
<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">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="serviceRecordModalContent">
|
<div class="modal-content" id="serviceRecordModalContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,4 +157,12 @@
|
|||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||||
</ul>
|
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
|
||||||
|
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||||
|
</ul>
|
||||||
|
@if (userColumnPreferences.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
|
||||||
|
}
|
||||||
@@ -67,14 +67,14 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="supplyRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
<label for="supplyRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<label for="supplyRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
<label for="supplyRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
var enableCsvImports = userConfig.EnableCsvImports;
|
var enableCsvImports = userConfig.EnableCsvImports;
|
||||||
var hideZero = userConfig.HideZero;
|
var hideZero = userConfig.HideZero;
|
||||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
if (userConfig.EnableExtraFieldColumns)
|
||||||
|
{
|
||||||
|
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
|
||||||
|
}
|
||||||
|
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.SupplyRecord);
|
||||||
}
|
}
|
||||||
@model List<SupplyRecord>
|
@model List<SupplyRecord>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -38,6 +44,61 @@
|
|||||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('SupplyRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('SupplyRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="searchTableRows('supply-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='partnumber' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_PartNumber" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_PartNumber">@translator.Translate(userLanguage, "Part Number")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='supplier' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Supplier" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Supplier">@translator.Translate(userLanguage, "Supplier")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Description" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='quantity' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Quantity" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Quantity">@translator.Translate(userLanguage, "Quantity")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="@elementId">
|
||||||
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -58,26 +119,34 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage,"Date")</th>
|
<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">@translator.Translate(userLanguage, "Part #")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="partnumber">@translator.Translate(userLanguage, "Part #")</th>
|
||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Supplier")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="supplier">@translator.Translate(userLanguage, "Supplier")</th>
|
||||||
<th scope="col" class="col-2 col-xl-3">@translator.Translate(userLanguage, "Description")</th>
|
<th scope="col" class="col-2 flex-grow-1 col-xl-3" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||||
<th scope="col" class="col-1" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Quantity")</th>
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1" data-column="quantity" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Quantity")</th>
|
||||||
<th scope="col" class="col-1" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Notes")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (SupplyRecord supplyRecord in Model)
|
@foreach (SupplyRecord supplyRecord in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@supplyRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditSupplyRecordModal,@supplyRecord.Id)" data-tags='@string.Join(" ", supplyRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@supplyRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditSupplyRecordModal,@supplyRecord.Id)" data-tags='@string.Join(" ", supplyRecord.Tags)'>
|
||||||
<td class="col-2 col-xl-1">@supplyRecord.Date.ToShortDateString()</td>
|
<td class="col-2 flex-grow-1 col-xl-1" data-column="date">@supplyRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-2">@supplyRecord.PartNumber</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="partnumber">@supplyRecord.PartNumber</td>
|
||||||
<td class="col-2">@supplyRecord.PartSupplier</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="supplier">@supplyRecord.PartSupplier</td>
|
||||||
<td class="col-2 col-xl-3">@supplyRecord.Description</td>
|
<td class="col-2 flex-grow-1 col-xl-3" data-column="description">@supplyRecord.Description</td>
|
||||||
<td class="col-1">@supplyRecord.Quantity</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="quantity">@supplyRecord.Quantity</td>
|
||||||
<td class="col-1" data-record-type="cost">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
|
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-2 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(supplyRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -99,4 +168,8 @@
|
|||||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@if (userColumnPreferences.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
|
||||||
|
}
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="taxRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
<label for="taxRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="taxRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<label for="taxRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
<label for="taxRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="taxRecordFiles">
|
<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>
|
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
var enableCsvImports = userConfig.EnableCsvImports;
|
var enableCsvImports = userConfig.EnableCsvImports;
|
||||||
var hideZero = userConfig.HideZero;
|
var hideZero = userConfig.HideZero;
|
||||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
if (userConfig.EnableExtraFieldColumns)
|
||||||
|
{
|
||||||
|
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
|
||||||
|
}
|
||||||
|
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.TaxRecord);
|
||||||
}
|
}
|
||||||
@model List<TaxRecord>
|
@model List<TaxRecord>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -38,6 +44,43 @@
|
|||||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('TaxRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('TaxRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="searchTableRows('tax-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Description" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="@elementId">
|
||||||
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -58,20 +101,28 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-3 col-xl-1">@translator.Translate(userLanguage, "Date")</th>
|
<th scope="col" class="col-3 flex-grow-1 col-xl-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
|
||||||
<th scope="col" class="col-4 col-xl-6">@translator.Translate(userLanguage, "Description")</th>
|
<th scope="col" class="col-4 flex-grow-1 col-xl-6" data-column="description">@translator.Translate(userLanguage, "Description")</th>
|
||||||
<th scope="col" class="col-2" onclick="toggleSort('tax-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('tax-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Notes")</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)
|
||||||
|
{
|
||||||
|
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (TaxRecord taxRecord in Model)
|
@foreach (TaxRecord taxRecord in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@taxRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditTaxRecordModal,@taxRecord.Id)" data-tags='@string.Join(" ", taxRecord.Tags)'>
|
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@taxRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditTaxRecordModal,@taxRecord.Id)" data-tags='@string.Join(" ", taxRecord.Tags)'>
|
||||||
<td class="col-3 col-xl-1">@taxRecord.Date.ToShortDateString()</td>
|
<td class="col-3 flex-grow-1 col-xl-1" data-column="date">@taxRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-4 col-xl-6">@taxRecord.Description</td>
|
<td class="col-4 flex-grow-1 col-xl-6" data-column="description">@taxRecord.Description</td>
|
||||||
<td class="col-2" data-record-type="cost">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(taxRecord.Notes)</td>
|
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(taxRecord.Notes)</td>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(taxRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -93,4 +144,8 @@
|
|||||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@if (userColumnPreferences.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
<div>
|
<div>
|
||||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
<label for="upgradeRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
<label for="upgradeRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="upgradeRecordFiles">
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<label for="upgradeRecordFiles">Upload documents(optional)</label>
|
<label for="upgradeRecordFiles">Upload documents(optional)</label>
|
||||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="upgradeRecordFiles">
|
<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>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
var enableCsvImports = userConfig.EnableCsvImports;
|
var enableCsvImports = userConfig.EnableCsvImports;
|
||||||
var hideZero = userConfig.HideZero;
|
var hideZero = userConfig.HideZero;
|
||||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
if (userConfig.EnableExtraFieldColumns)
|
||||||
|
{
|
||||||
|
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
|
||||||
|
}
|
||||||
|
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.UpgradeRecord);
|
||||||
}
|
}
|
||||||
@model List<UpgradeRecord>
|
@model List<UpgradeRecord>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -38,6 +44,49 @@
|
|||||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('UpgradeRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('UpgradeRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="searchTableRows('upgrade-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Date" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Odometer" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Description" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Cost" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Notes" checked>
|
||||||
|
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (string extraFieldColumn in extraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="@elementId">
|
||||||
|
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -58,22 +107,30 @@
|
|||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
<tr class="d-flex">
|
<tr class="d-flex">
|
||||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage, "Date")</th>
|
<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">@translator.Translate(userLanguage, "Odometer")</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 col-xl-4">@translator.Translate(userLanguage, "Description")</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-2" onclick="toggleSort('upgrade-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</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">@translator.Translate(userLanguage, "Notes")</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)
|
||||||
|
{
|
||||||
|
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (UpgradeRecord upgradeRecord in Model)
|
@foreach (UpgradeRecord upgradeRecord in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex user-select-none" style="cursor:pointer;" 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)'>
|
<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 col-xl-1">@upgradeRecord.Date.ToShortDateString()</td>
|
<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">@upgradeRecord.Mileage</td>
|
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@upgradeRecord.Mileage</td>
|
||||||
<td class="col-3 col-xl-4">@upgradeRecord.Description</td>
|
<td class="col-3 flex-grow-1 col-xl-4" data-column="description">@upgradeRecord.Description</td>
|
||||||
<td class="col-2" data-record-type="cost">@((hideZero && upgradeRecord.Cost == default) ? "---" : upgradeRecord.Cost.ToString("C"))</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 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(upgradeRecord.Notes)</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)
|
||||||
|
{
|
||||||
|
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(upgradeRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
|
||||||
|
}
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -99,4 +156,12 @@
|
|||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||||
</ul>
|
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
|
||||||
|
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||||
|
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||||
|
</ul>
|
||||||
|
@if (userColumnPreferences.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
|
||||||
|
}
|
||||||
9
Views/Vehicle/_UserColumnPreferences.cshtml
Normal file
9
Views/Vehicle/_UserColumnPreferences.cshtml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@model IEnumerable<UserColumnPreference>
|
||||||
|
<script>
|
||||||
|
var visibleColumns = [];
|
||||||
|
@foreach(string visibleColumn in Model.SelectMany(x=> x.VisibleColumns))
|
||||||
|
{
|
||||||
|
@:visibleColumns.push(decodeHTMLEntities('@visibleColumn'));
|
||||||
|
}
|
||||||
|
loadUserColumnPreferences(visibleColumns);
|
||||||
|
</script>
|
||||||
@@ -25,14 +25,30 @@
|
|||||||
<span class="lead">@Model.VehicleData.LicensePlate</span>
|
<span class="lead">@Model.VehicleData.LicensePlate</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
@if (Model.VehicleData.IsElectric)
|
<div class="row">
|
||||||
{
|
<div class="col-4">
|
||||||
<span><i class="bi bi-ev-station me-2"></i>@translator.Translate(userLanguage, "Electric")</span>
|
@if (Model.VehicleData.IsElectric)
|
||||||
}
|
{
|
||||||
else
|
<span><i class="bi bi-ev-station me-2"></i>@translator.Translate(userLanguage, "Electric")</span>
|
||||||
{
|
}
|
||||||
<span><i class="bi bi-fuel-pump me-2"></i>@translator.Translate(userLanguage, "Gasoline")</span>
|
else
|
||||||
}
|
{
|
||||||
|
<span><i class="bi bi-fuel-pump me-2"></i>@translator.Translate(userLanguage, "Gasoline")</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.DaysOwned))
|
||||||
|
{
|
||||||
|
<div class="col-4">
|
||||||
|
<span><i class="bi bi-calendar-range me-2"></i>@($"{Model.DaysOwned} {translator.Translate(userLanguage, "Days")}")</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (Model.DistanceTraveled != default)
|
||||||
|
{
|
||||||
|
<div class="col-4">
|
||||||
|
<span><i class="bi bi-speedometer me-2"></i>@($"{Model.DistanceTraveled} {Model.DistanceUnit}")</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,8 +56,8 @@
|
|||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Last Reported Odometer Reading")}: {Model.Odometer}") </li>
|
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Last Reported Odometer Reading")}: {Model.Odometer}") </li>
|
||||||
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Average Fuel Economy")}: {Model.MPG}") </li>
|
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Average Fuel Economy")}: {Model.MPG}") </li>
|
||||||
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Total Spent(excl. fuel)")}: {Model.TotalCost.ToString("C")}") </li>
|
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Total Spent(excl. fuel)")}: {Model.TotalCost.ToString("C")} ({Model.TotalCostPerMile.ToString("C")}/{Model.DistanceUnit})") </li>
|
||||||
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Total Spent on Fuel")}: {Model.TotalGasCost.ToString("C")}") </li>
|
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Total Spent on Fuel")}: {Model.TotalGasCost.ToString("C")} ({Model.TotalGasCostPerMile.ToString("C")}/{Model.DistanceUnit})") </li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,38 +26,69 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputYear">@translator.Translate(userLanguage, "Year")</label>
|
<div class="row">
|
||||||
<input type="number" inputmode="numeric" id="inputYear" class="form-control" placeholder="@translator.Translate(userLanguage, "Year(must be after 1900)")" value="@(isNew ? "" : Model.Year)">
|
<div class="col-12 col-md-6">
|
||||||
<label for="inputMake">@translator.Translate(userLanguage, "Make")</label>
|
<label for="inputYear">@translator.Translate(userLanguage, "Year")</label>
|
||||||
<input type="text" id="inputMake" class="form-control" placeholder="@translator.Translate(userLanguage, "Make")" value="@Model.Make">
|
<input type="number" inputmode="numeric" id="inputYear" class="form-control" placeholder="@translator.Translate(userLanguage, "Year(must be after 1900)")" value="@(isNew ? "" : Model.Year)">
|
||||||
<label for="inputModel">@translator.Translate(userLanguage, "Model")</label>
|
<label for="inputMake">@translator.Translate(userLanguage, "Make")</label>
|
||||||
<input type="text" id="inputModel" class="form-control" placeholder="@translator.Translate(userLanguage, "Model")" value="@Model.Model">
|
<input type="text" id="inputMake" class="form-control" placeholder="@translator.Translate(userLanguage, "Make")" value="@Model.Make">
|
||||||
<label for="inputLicensePlate">@translator.Translate(userLanguage, "License Plate")</label>
|
<label for="inputModel">@translator.Translate(userLanguage, "Model")</label>
|
||||||
<input type="text" id="inputLicensePlate" class="form-control" placeholder="@translator.Translate(userLanguage, "License Plate")" value="@Model.LicensePlate">
|
<input type="text" id="inputModel" class="form-control" placeholder="@translator.Translate(userLanguage, "Model")" value="@Model.Model">
|
||||||
<div class="form-check form-switch">
|
<label for="inputLicensePlate">@translator.Translate(userLanguage, "License Plate")</label>
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="inputIsElectric" checked="@Model.IsElectric">
|
<input type="text" id="inputLicensePlate" class="form-control" placeholder="@translator.Translate(userLanguage, "License Plate")" value="@Model.LicensePlate">
|
||||||
<label class="form-check-label" for="inputIsElectric">@translator.Translate(userLanguage, "Electric Vehicle")</label>
|
@foreach (ExtraField field in Model.ExtraFields)
|
||||||
|
{
|
||||||
|
var elementId = Guid.NewGuid();
|
||||||
|
<div class="extra-field">
|
||||||
|
<label for="@elementId">@field.Name</label>
|
||||||
|
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<label for="inputPurchaseDate">@translator.Translate(userLanguage, "Purchased Date(optional)")</label>
|
||||||
|
<input type="text" id="inputPurchaseDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Date")" value="@Model.PurchaseDate">
|
||||||
|
<label for="inputSoldDate">@translator.Translate(userLanguage, "Sold Date(optional)")</label>
|
||||||
|
<input type="text" id="inputSoldDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Date")" value="@Model.SoldDate">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" onchange="toggleOdometerAdjustment()" id="inputHasOdometerAdjustment" checked="@Model.HasOdometerAdjustment">
|
||||||
|
<label class="form-check-label" for="inputHasOdometerAdjustment">@translator.Translate(userLanguage, "Odometer Adjustments")</label>
|
||||||
|
</div>
|
||||||
|
<div class="collapse @(Model.HasOdometerAdjustment ? "show" : "")" id="odometerAdjustments">
|
||||||
|
<div>
|
||||||
|
<label for="inputOdometerMultiplier">@translator.Translate(userLanguage, "Odometer Multiplier")</label>
|
||||||
|
<input type="text" id="inputOdometerMultiplier" class="form-control" placeholder="@translator.Translate(userLanguage, "Odometer Multiplier")" value="@Model.OdometerMultiplier">
|
||||||
|
<label for="inputOdometerDifference">@translator.Translate(userLanguage, "Odometer Difference")</label>
|
||||||
|
<input type="text" id="inputOdometerDifference" class="form-control" placeholder="@translator.Translate(userLanguage, "Odometer Difference")" value="@Model.OdometerDifference">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label for="inputTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
|
||||||
|
<select multiple class="form-select" id="inputTag">
|
||||||
|
@foreach (string tag in Model.Tags)
|
||||||
|
{
|
||||||
|
<!option value="@tag">@tag</!option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.ImageLocation))
|
||||||
|
{
|
||||||
|
<label for="inputImage">@translator.Translate(userLanguage, "Replace picture(optional)")</label>
|
||||||
|
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<label for="inputImage">@translator.Translate(userLanguage, "Upload a picture(optional)")</label>
|
||||||
|
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<label for="inputTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
|
|
||||||
<select multiple class="form-select" id="inputTag">
|
|
||||||
@foreach (string tag in Model.Tags)
|
|
||||||
{
|
|
||||||
<!option value="@tag">@tag</!option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.ImageLocation))
|
|
||||||
{
|
|
||||||
<label for="inputImage">@translator.Translate(userLanguage, "Replace picture(optional)")</label>
|
|
||||||
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
<label for="inputImage">@translator.Translate(userLanguage, "Upload a picture(optional)")</label>
|
|
||||||
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,10 +15,13 @@
|
|||||||
"EnableAutoReminderRefresh": false,
|
"EnableAutoReminderRefresh": false,
|
||||||
"EnableAutoOdometerInsert": false,
|
"EnableAutoOdometerInsert": false,
|
||||||
"EnableShopSupplies": false,
|
"EnableShopSupplies": false,
|
||||||
|
"EnableExtraFieldColumns": false,
|
||||||
"UseUKMPG": false,
|
"UseUKMPG": false,
|
||||||
"UseThreeDecimalGasCost": true,
|
"UseThreeDecimalGasCost": true,
|
||||||
"UseMarkDownOnSavedNotes": false,
|
"UseMarkDownOnSavedNotes": false,
|
||||||
|
"HideSoldVehicles": false,
|
||||||
"PreferredGasMileageUnit": "",
|
"PreferredGasMileageUnit": "",
|
||||||
|
"UserColumnPreferences": [],
|
||||||
"PreferredGasUnit": "",
|
"PreferredGasUnit": "",
|
||||||
"UserLanguage": "en_US",
|
"UserLanguage": "en_US",
|
||||||
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
|
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
|
||||||
|
|||||||
45
docker-compose.postgresql.yml
Normal file
45
docker-compose.postgresql.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: ghcr.io/hargata/lubelogger:latest
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
# volumes used to keep data persistent
|
||||||
|
volumes:
|
||||||
|
- config:/App/config
|
||||||
|
- data:/App/data
|
||||||
|
- translations:/App/wwwroot/translations
|
||||||
|
- documents:/App/wwwroot/documents
|
||||||
|
- images:/App/wwwroot/images
|
||||||
|
- temp:/App/wwwroot/temp
|
||||||
|
- log:/App/log
|
||||||
|
- keys:/root/.aspnet/DataProtection-Keys
|
||||||
|
# expose port and/or use serving via traefik
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:14
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: "lubelogger"
|
||||||
|
POSTGRES_PASSWORD: "lubepass"
|
||||||
|
POSTGRES_DB: "lubelogger"
|
||||||
|
volumes:
|
||||||
|
- postgres:/var/lib/postgresql/data
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
config:
|
||||||
|
data:
|
||||||
|
translations:
|
||||||
|
documents:
|
||||||
|
images:
|
||||||
|
temp:
|
||||||
|
log:
|
||||||
|
keys:
|
||||||
|
postgres:
|
||||||
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -2,6 +2,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<title>LubeLogger</title>
|
<title>LubeLogger</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||||
<style>
|
<style>
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#features">Features</a></div>
|
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#features">Features</a></div>
|
||||||
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#demo">Demo</a></div>
|
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#demo">Demo</a></div>
|
||||||
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#download">Download</a></div>
|
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#download">Download</a></div>
|
||||||
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="https://docs.lubelogger.com">Docs</a></div>
|
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="https://docs.lubelogger.com">Documentation</a></div>
|
||||||
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="https://github.com/hargata/lubelog">GitHub Repo</a></div>
|
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="https://github.com/hargata/lubelog">GitHub Repo</a></div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
@@ -42,12 +43,12 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="carouselGallery" class="carousel slide">
|
<div id="carouselGallery" class="carousel slide">
|
||||||
<div class="carousel-indicators">
|
<div class="carousel-indicators">
|
||||||
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="0" class="active" aria-current="true"></button>
|
<button type="button" data-bs-target="#carouselGallery" style='background-color: #fff' data-bs-slide-to="0" class="active" aria-current="true"></button>
|
||||||
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="1"></button>
|
<button type="button" data-bs-target="#carouselGallery" style='background-color: #fff' data-bs-slide-to="1"></button>
|
||||||
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="2"></button>
|
<button type="button" data-bs-target="#carouselGallery" style='background-color: #fff' data-bs-slide-to="2"></button>
|
||||||
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="3"></button>
|
<button type="button" data-bs-target="#carouselGallery" style='background-color: #fff' data-bs-slide-to="3"></button>
|
||||||
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="4"></button>
|
<button type="button" data-bs-target="#carouselGallery" style='background-color: #fff' data-bs-slide-to="4"></button>
|
||||||
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="5"></button>
|
<button type="button" data-bs-target="#carouselGallery" style='background-color: #fff' data-bs-slide-to="5"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="carousel-inner">
|
<div class="carousel-inner">
|
||||||
<div class="carousel-item active">
|
<div class="carousel-item active">
|
||||||
@@ -94,11 +95,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselGallery" data-bs-slide="prev">
|
<button class="carousel-control-prev" type="button" data-bs-target="#carouselGallery" data-bs-slide="prev">
|
||||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
<span class="carousel-control-prev-icon" style='filter:inherit;' aria-hidden="true"></span>
|
||||||
<span class="visually-hidden">Previous</span>
|
<span class="visually-hidden">Previous</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselGallery" data-bs-slide="next">
|
<button class="carousel-control-next" type="button" data-bs-target="#carouselGallery" data-bs-slide="next">
|
||||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
<span class="carousel-control-next-icon" style='filter:inherit;' aria-hidden="true"></span>
|
||||||
<span class="visually-hidden">Next</span>
|
<span class="visually-hidden">Next</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,7 +142,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Live demo available <a href="https://demo.lubelogger.com">here</a>
|
Live demo available <a class='link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover' href="https://demo.lubelogger.com">here</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
@@ -151,44 +152,20 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<div class="row" id="download">
|
<div class="row" id="download">
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<h6 class="display-6 text-center">Where to Download</h6>
|
<h6 class="display-6 text-center">Download</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
LubeLogger is released exclusively as a Docker Image via the GitHub Container Repository(GHCR)
|
LubeLogger is available as both a Docker Image and a Windows Standalone Executable(EXE)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
To pull down the Docker Image, run
|
Read this <a class='link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover' href='https://docs.lubelogger.com/Getting%20Started'>Getting Started Guide</a> on how to download either of them
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex justify-content-center">
|
|
||||||
<p><code>docker pull ghcr.io/hargata/lubelogger:latest</code></p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 d-flex justify-content-center">
|
|
||||||
<p class="lead">
|
|
||||||
Check the <a href="https://github.com/hargata/lubelog/" target="_blank">GitHub repository</a> and clone the "docker-compose.yml" and ".env" file onto your computer.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 d-flex justify-content-center">
|
|
||||||
<p class="lead">
|
|
||||||
Navigate to the directory where those two files are located, then run
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 d-flex justify-content-center">
|
|
||||||
<p><code>docker-compose up</code></p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 d-flex justify-content-center">
|
|
||||||
<p class="lead">
|
|
||||||
Navigate to the URL the service is listening on, the default is
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 d-flex justify-content-center">
|
|
||||||
<p><code>http://localhost:8080</code></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -196,15 +173,11 @@
|
|||||||
<h6 class="display-6 text-center">About</h6>
|
<h6 class="display-6 text-center">About</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<p class="lead">LubeLogger is proudly developed in Price Utah by Hargata Softworks.
|
<p class="lead">LubeLogger is proudly developed in <a class='link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover' href="https://www.visitutah.com/Places-To-Go/Cities-and-Towns/Price" target="_blank">Price Utah</a> by <a class='link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover' href='mailto:hargatasoftworks@gmail.com'>Hargata Softworks</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<p class="lead"><a href="https://www.patreon.com/LubeLogger">Support us on Patreon</a>
|
<p class="lead"><a class='link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover' href="https://www.patreon.com/LubeLogger">Support us on Patreon</a> or <a class='link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover' href='https://buy.stripe.com/aEU9Egc8DdMc9bO144'>Donate on Stripe</a>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 d-flex justify-content-center">
|
|
||||||
<p class="lead">For more information regarding Price Utah, click <a href="https://www.visitutah.com/Places-To-Go/Cities-and-Towns/Price" target="_blank">here</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ html {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
}
|
}
|
||||||
@@ -358,4 +362,15 @@ input[type="file"] {
|
|||||||
.zero-y-padding{
|
.zero-y-padding{
|
||||||
padding-top: 0rem;
|
padding-top: 0rem;
|
||||||
padding-bottom: 0rem;
|
padding-bottom: 0rem;
|
||||||
|
}
|
||||||
|
.vehicle-sold-banner {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.copyable{
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -86,6 +86,8 @@ function getAndValidateCollisionRecordValues() {
|
|||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var collisionRecordId = getCollisionRecordModelData().id;
|
var collisionRecordId = getCollisionRecordModelData().id;
|
||||||
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
||||||
|
//Odometer Adjustments
|
||||||
|
collisionMileage = GetAdjustedOdometer(collisionRecordId, collisionMileage);
|
||||||
//validation
|
//validation
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
var extraFields = getAndValidateExtraFields();
|
var extraFields = getAndValidateExtraFields();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
if (data) {
|
if (data) {
|
||||||
$("#addVehicleModalContent").html(data);
|
$("#addVehicleModalContent").html(data);
|
||||||
initTagSelector($("#inputTag"));
|
initTagSelector($("#inputTag"));
|
||||||
|
initDatePicker($('#inputPurchaseDate'));
|
||||||
|
initDatePicker($('#inputSoldDate'));
|
||||||
$('#addVehicleModal').modal('show');
|
$('#addVehicleModal').modal('show');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -33,7 +35,7 @@ function getVehicleSupplyRecords() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function GetVehicleId() {
|
function GetVehicleId() {
|
||||||
return { vehicleId: 0 };
|
return { vehicleId: 0, hasOdometerAdjustment: false };
|
||||||
}
|
}
|
||||||
function bindTabEvent() {
|
function bindTabEvent() {
|
||||||
$('button[data-bs-toggle="tab"]').on('show.bs.tab', function (e) {
|
$('button[data-bs-toggle="tab"]').on('show.bs.tab', function (e) {
|
||||||
@@ -171,27 +173,8 @@ function performLogOut() {
|
|||||||
}
|
}
|
||||||
function loadPinnedNotes(vehicleId) {
|
function loadPinnedNotes(vehicleId) {
|
||||||
var hoveredGrid = $(`#gridVehicle_${vehicleId}`);
|
var hoveredGrid = $(`#gridVehicle_${vehicleId}`);
|
||||||
if (hoveredGrid.attr("data-bs-title") == undefined) {
|
if (hoveredGrid.attr("data-bs-title") != '') {
|
||||||
$.get(`/Vehicle/GetPinnedNotesByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
hoveredGrid.tooltip("show");
|
||||||
if (data.length > 0) {
|
|
||||||
//converted pinned notes to html.
|
|
||||||
var htmlString = "<ul class='list-group list-group-flush'>";
|
|
||||||
data.forEach(x => {
|
|
||||||
htmlString += `<li><b>${x.description}</b> : ${x.noteText}</li>`;
|
|
||||||
});
|
|
||||||
htmlString += "</ul>";
|
|
||||||
hoveredGrid.attr("data-bs-title", htmlString);
|
|
||||||
new bootstrap.Tooltip(hoveredGrid);
|
|
||||||
hoveredGrid.tooltip("show");
|
|
||||||
} else {
|
|
||||||
//disable the tooltip
|
|
||||||
hoveredGrid.attr("data-bs-title", "");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (hoveredGrid.attr("data-bs-title") != '') {
|
|
||||||
hoveredGrid.tooltip("show");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function hidePinnedNotes(vehicleId) {
|
function hidePinnedNotes(vehicleId) {
|
||||||
@@ -350,4 +333,64 @@ function copyContributors(sourceVehicleId, destVehicleId) {
|
|||||||
$("#workAroundInput").hide();
|
$("#workAroundInput").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAccountInformationModal() {
|
||||||
|
$.get('/Home/GetUserAccountInformationModal', function (data) {
|
||||||
|
$('#accountInformationModalContent').html(data);
|
||||||
|
$('#accountInformationModal').modal('show');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function hideAccountInformationModal() {
|
||||||
|
$('#accountInformationModal').modal('hide');
|
||||||
|
}
|
||||||
|
function validateAndSaveUserAccount() {
|
||||||
|
var hasError = false;
|
||||||
|
if ($('#inputUsername').val().trim() == '') {
|
||||||
|
$('#inputUsername').addClass("is-invalid");
|
||||||
|
hasError = true;
|
||||||
|
} else {
|
||||||
|
$('#inputUsername').removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if ($('#inputEmail').val().trim() == '') {
|
||||||
|
$('#inputEmail').addClass("is-invalid");
|
||||||
|
hasError = true;
|
||||||
|
} else {
|
||||||
|
$('#inputEmail').removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if ($('#inputToken').val().trim() == '') {
|
||||||
|
$('#inputToken').addClass("is-invalid");
|
||||||
|
hasError = true;
|
||||||
|
} else {
|
||||||
|
$('#inputToken').removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (hasError) {
|
||||||
|
errorToast("Please check the form data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var userAccountInfo = {
|
||||||
|
userName: $('#inputUsername').val(),
|
||||||
|
password: $('#inputPassword').val(),
|
||||||
|
emailAddress: $('#inputEmail').val(),
|
||||||
|
token: $('#inputToken').val()
|
||||||
|
}
|
||||||
|
$.post('/Home/UpdateUserAccount', { userAccount: userAccountInfo }, function (data) {
|
||||||
|
if (data.success) {
|
||||||
|
//hide modal
|
||||||
|
hideAccountInformationModal();
|
||||||
|
successToast('Profile Updated');
|
||||||
|
performLogOut();
|
||||||
|
} else {
|
||||||
|
errorToast(data.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function generateTokenForUser() {
|
||||||
|
$.post('/Home/GenerateTokenForUser', function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast('Token sent');
|
||||||
|
} else {
|
||||||
|
errorToast(genericErrorMessage())
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -85,6 +85,8 @@ function getAndValidateGasRecordValues() {
|
|||||||
var gasTags = $("#gasRecordTag").val();
|
var gasTags = $("#gasRecordTag").val();
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var gasRecordId = getGasRecordModelData().id;
|
var gasRecordId = getGasRecordModelData().id;
|
||||||
|
//Odometer Adjustments
|
||||||
|
gasMileage = GetAdjustedOdometer(gasRecordId, gasMileage);
|
||||||
//validation
|
//validation
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
var extraFields = getAndValidateExtraFields();
|
var extraFields = getAndValidateExtraFields();
|
||||||
@@ -384,4 +386,109 @@ function toggleUnits(sender) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchGasTableRows() {
|
||||||
|
var tabName = 'gas-tab-pane';
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Search Records',
|
||||||
|
html: `
|
||||||
|
<input type="text" id="inputSearch" class="swal2-input" placeholder="Keyword(case sensitive)">
|
||||||
|
`,
|
||||||
|
confirmButtonText: 'Search',
|
||||||
|
focusConfirm: false,
|
||||||
|
preConfirm: () => {
|
||||||
|
const searchString = $("#inputSearch").val();
|
||||||
|
return { searchString }
|
||||||
|
},
|
||||||
|
}).then(function (result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
var rowData = $(`#${tabName} table tbody tr`);
|
||||||
|
var filteredRows = $(`#${tabName} table tbody tr td:contains('${result.value.searchString}')`).parent();
|
||||||
|
var splitSearchString = result.value.searchString.split('=');
|
||||||
|
if (result.value.searchString.includes('=') && splitSearchString.length == 2) {
|
||||||
|
//column specific search.
|
||||||
|
//get column index
|
||||||
|
var columns = $(`#${tabName} table th`).toArray().map(x => x.innerText);
|
||||||
|
var columnName = splitSearchString[0];
|
||||||
|
var colSearchString = splitSearchString[1];
|
||||||
|
var colIndex = columns.findIndex(x => x == columnName) + 1;
|
||||||
|
filteredRows = $(`#${tabName} table tbody tr td:nth-child(${colIndex}):contains('${colSearchString}')`).parent();
|
||||||
|
}
|
||||||
|
if (result.value.searchString.trim() == '') {
|
||||||
|
rowData.removeClass('override-hide');
|
||||||
|
} else {
|
||||||
|
rowData.addClass('override-hide');
|
||||||
|
filteredRows.removeClass('override-hide');
|
||||||
|
}
|
||||||
|
$(".tagfilter.bg-primary").addClass('bg-secondary').removeClass('bg-primary');
|
||||||
|
updateAggregateLabels();
|
||||||
|
updateMPGLabels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function editMultipleGasRecords(ids) {
|
||||||
|
$.post('/Vehicle/GetGasRecordsEditModal', { recordIds: ids }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#gasRecordModalContent").html(data);
|
||||||
|
//initiate datepicker
|
||||||
|
initDatePicker($('#gasRecordDate'));
|
||||||
|
initTagSelector($("#gasRecordTag"));
|
||||||
|
$('#gasRecordModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function saveMultipleGasRecordsToVehicle() {
|
||||||
|
var gasDate = $("#gasRecordDate").val();
|
||||||
|
var gasMileage = $("#gasRecordMileage").val();
|
||||||
|
var gasMileageToParse = parseInt(globalParseFloat($("#gasRecordMileage").val())).toString();
|
||||||
|
var gasConsumption = $("#gasRecordConsumption").val();
|
||||||
|
var gasCost = $("#gasRecordCost").val();
|
||||||
|
var gasNotes = $("#gasRecordNotes").val();
|
||||||
|
var gasTags = $("#gasRecordTag").val();
|
||||||
|
//validation
|
||||||
|
var hasError = false;
|
||||||
|
if (gasMileage.trim() != '' && (isNaN(gasMileageToParse) || parseInt(gasMileageToParse) < 0)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#gasRecordMileage").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#gasRecordMileage").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (gasConsumption.trim() != '' && !isValidMoney(gasConsumption)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#gasRecordConsumption").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#gasRecordConsumption").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (gasCost.trim() != '' && !isValidMoney(gasCost)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#gasRecordCost").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#gasRecordCost").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (hasError) {
|
||||||
|
errorToast("Please check the form data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var formValues = {
|
||||||
|
recordIds: recordsToEdit,
|
||||||
|
editRecord: {
|
||||||
|
date: gasDate,
|
||||||
|
mileage: gasMileageToParse,
|
||||||
|
gallons: gasConsumption,
|
||||||
|
cost: gasCost,
|
||||||
|
notes: gasNotes,
|
||||||
|
tags: gasTags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$.post('/Vehicle/SaveMultipleGasRecords', { editModel: formValues }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast("Gas Records Updated");
|
||||||
|
hideAddGasRecordModal();
|
||||||
|
saveScrollPosition();
|
||||||
|
getVehicleGasRecords(GetVehicleId().vehicleId);
|
||||||
|
} else {
|
||||||
|
errorToast(genericErrorMessage());
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
function showAddOdometerRecordModal() {
|
function showAddOdometerRecordModal() {
|
||||||
$.get('/Vehicle/GetAddOdometerRecordPartialView', function (data) {
|
$.get(`/Vehicle/GetAddOdometerRecordPartialView?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
$("#odometerRecordModalContent").html(data);
|
$("#odometerRecordModalContent").html(data);
|
||||||
//initiate datepicker
|
//initiate datepicker
|
||||||
@@ -78,11 +78,14 @@ function saveOdometerRecordToVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
function getAndValidateOdometerRecordValues() {
|
function getAndValidateOdometerRecordValues() {
|
||||||
var serviceDate = $("#odometerRecordDate").val();
|
var serviceDate = $("#odometerRecordDate").val();
|
||||||
|
var initialOdometerMileage = parseInt(globalParseFloat($("#initialOdometerRecordMileage").val())).toString();
|
||||||
var serviceMileage = parseInt(globalParseFloat($("#odometerRecordMileage").val())).toString();
|
var serviceMileage = parseInt(globalParseFloat($("#odometerRecordMileage").val())).toString();
|
||||||
var serviceNotes = $("#odometerRecordNotes").val();
|
var serviceNotes = $("#odometerRecordNotes").val();
|
||||||
var serviceTags = $("#odometerRecordTag").val();
|
var serviceTags = $("#odometerRecordTag").val();
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var odometerRecordId = getOdometerRecordModelData().id;
|
var odometerRecordId = getOdometerRecordModelData().id;
|
||||||
|
//Odometer Adjustments
|
||||||
|
serviceMileage = GetAdjustedOdometer(odometerRecordId, serviceMileage);
|
||||||
//validation
|
//validation
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
var extraFields = getAndValidateExtraFields();
|
var extraFields = getAndValidateExtraFields();
|
||||||
@@ -101,15 +104,95 @@ function getAndValidateOdometerRecordValues() {
|
|||||||
} else {
|
} else {
|
||||||
$("#odometerRecordMileage").removeClass("is-invalid");
|
$("#odometerRecordMileage").removeClass("is-invalid");
|
||||||
}
|
}
|
||||||
|
if (isNaN(initialOdometerMileage) || parseInt(initialOdometerMileage) < 0) {
|
||||||
|
hasError = true;
|
||||||
|
$("#initialOdometerRecordMileage").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#initialOdometerRecordMileage").removeClass("is-invalid");
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
id: odometerRecordId,
|
id: odometerRecordId,
|
||||||
hasError: hasError,
|
hasError: hasError,
|
||||||
vehicleId: vehicleId,
|
vehicleId: vehicleId,
|
||||||
date: serviceDate,
|
date: serviceDate,
|
||||||
|
initialMileage: initialOdometerMileage,
|
||||||
mileage: serviceMileage,
|
mileage: serviceMileage,
|
||||||
notes: serviceNotes,
|
notes: serviceNotes,
|
||||||
tags: serviceTags,
|
tags: serviceTags,
|
||||||
files: uploadedFiles,
|
files: uploadedFiles,
|
||||||
extraFields: extraFields.extraFields
|
extraFields: extraFields.extraFields
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalculateDistance() {
|
||||||
|
//force distance recalculation
|
||||||
|
//reserved for when data is incoherent with negative distances due to non-chronologica order of odometer records.
|
||||||
|
var vehicleId = GetVehicleId().vehicleId
|
||||||
|
$.post(`/Vehicle/ForceRecalculateDistanceByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast("Odometer Records Updated")
|
||||||
|
getVehicleOdometerRecords(vehicleId);
|
||||||
|
} else {
|
||||||
|
errorToast(genericErrorMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editMultipleOdometerRecords(ids) {
|
||||||
|
$.post('/Vehicle/GetOdometerRecordsEditModal', { recordIds: ids }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#odometerRecordModalContent").html(data);
|
||||||
|
//initiate datepicker
|
||||||
|
initDatePicker($('#odometerRecordDate'));
|
||||||
|
initTagSelector($("#odometerRecordTag"));
|
||||||
|
$('#odometerRecordModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function saveMultipleOdometerRecordsToVehicle() {
|
||||||
|
var odometerDate = $("#odometerRecordDate").val();
|
||||||
|
var initialOdometerMileage = $("#initialOdometerRecordMileage").val();
|
||||||
|
var odometerMileage = $("#odometerRecordMileage").val();
|
||||||
|
var initialOdometerMileageToParse = parseInt(globalParseFloat($("#initialOdometerRecordMileage").val())).toString();
|
||||||
|
var odometerMileageToParse = parseInt(globalParseFloat($("#odometerRecordMileage").val())).toString();
|
||||||
|
var odometerNotes = $("#odometerRecordNotes").val();
|
||||||
|
var odometerTags = $("#odometerRecordTag").val();
|
||||||
|
//validation
|
||||||
|
var hasError = false;
|
||||||
|
if (odometerMileage.trim() != '' && (isNaN(odometerMileageToParse) || parseInt(odometerMileageToParse) < 0)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#odometerRecordMileage").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#odometerRecordMileage").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (initialOdometerMileage.trim() != '' && (isNaN(initialOdometerMileageToParse) || parseInt(initialOdometerMileageToParse) < 0)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#odometerRecordMileage").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#odometerRecordMileage").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (hasError) {
|
||||||
|
errorToast("Please check the form data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var formValues = {
|
||||||
|
recordIds: recordsToEdit,
|
||||||
|
editRecord: {
|
||||||
|
date: odometerDate,
|
||||||
|
initialMileage: initialOdometerMileageToParse,
|
||||||
|
mileage: odometerMileageToParse,
|
||||||
|
notes: odometerNotes,
|
||||||
|
tags: odometerTags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$.post('/Vehicle/SaveMultipleOdometerRecords', { editModel: formValues }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast("Odometer Records Updated");
|
||||||
|
hideAddOdometerRecordModal();
|
||||||
|
saveScrollPosition();
|
||||||
|
getVehicleOdometerRecords(GetVehicleId().vehicleId);
|
||||||
|
} else {
|
||||||
|
errorToast(genericErrorMessage());
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,9 @@ function updatePlanRecordProgress(newProgress) {
|
|||||||
},
|
},
|
||||||
}).then(function (result) {
|
}).then(function (result) {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
$.post('/Vehicle/UpdatePlanRecordProgress', { planRecordId: draggedId, planProgress: newProgress, odometer: result.value.odometer }, function (data) {
|
//Odometer Adjustments
|
||||||
|
var adjustedOdometer = GetAdjustedOdometer(0, result.value.odometer);
|
||||||
|
$.post('/Vehicle/UpdatePlanRecordProgress', { planRecordId: draggedId, planProgress: newProgress, odometer: adjustedOdometer }, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
successToast("Plan Progress Updated");
|
successToast("Plan Progress Updated");
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ function getAndValidateServiceRecordValues() {
|
|||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var serviceRecordId = getServiceRecordModelData().id;
|
var serviceRecordId = getServiceRecordModelData().id;
|
||||||
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
||||||
|
//Odometer Adjustments
|
||||||
|
serviceMileage = GetAdjustedOdometer(serviceRecordId, serviceMileage);
|
||||||
//validation
|
//validation
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
var extraFields = getAndValidateExtraFields();
|
var extraFields = getAndValidateExtraFields();
|
||||||
|
|||||||
@@ -37,11 +37,20 @@ function saveVehicle(isEdit) {
|
|||||||
var vehicleMake = $("#inputMake").val();
|
var vehicleMake = $("#inputMake").val();
|
||||||
var vehicleModel = $("#inputModel").val();
|
var vehicleModel = $("#inputModel").val();
|
||||||
var vehicleTags = $("#inputTag").val();
|
var vehicleTags = $("#inputTag").val();
|
||||||
|
var vehiclePurchaseDate = $("#inputPurchaseDate").val();
|
||||||
|
var vehicleSoldDate = $("#inputSoldDate").val();
|
||||||
var vehicleLicensePlate = $("#inputLicensePlate").val();
|
var vehicleLicensePlate = $("#inputLicensePlate").val();
|
||||||
var vehicleIsElectric = $("#inputIsElectric").is(":checked");
|
var vehicleIsElectric = $("#inputIsElectric").is(":checked");
|
||||||
var vehicleUseHours = $("#inputUseHours").is(":checked");
|
var vehicleUseHours = $("#inputUseHours").is(":checked");
|
||||||
|
var vehicleHasOdometerAdjustment = $("#inputHasOdometerAdjustment").is(':checked');
|
||||||
|
var vehicleOdometerMultiplier = $("#inputOdometerMultiplier").val();
|
||||||
|
var vehicleOdometerDifference = parseInt(globalParseFloat($("#inputOdometerDifference").val())).toString();
|
||||||
|
var extraFields = getAndValidateExtraFields(true);
|
||||||
//validate
|
//validate
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
|
if (extraFields.hasError) {
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
if (vehicleYear.trim() == '' || parseInt(vehicleYear) < 1900) {
|
if (vehicleYear.trim() == '' || parseInt(vehicleYear) < 1900) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
$("#inputYear").addClass("is-invalid");
|
$("#inputYear").addClass("is-invalid");
|
||||||
@@ -66,6 +75,23 @@ function saveVehicle(isEdit) {
|
|||||||
} else {
|
} else {
|
||||||
$("#inputLicensePlate").removeClass("is-invalid");
|
$("#inputLicensePlate").removeClass("is-invalid");
|
||||||
}
|
}
|
||||||
|
if (vehicleHasOdometerAdjustment) {
|
||||||
|
//validate odometer adjustments
|
||||||
|
//validate multiplier
|
||||||
|
if (vehicleOdometerMultiplier.trim() == '' || !isValidMoney(vehicleOdometerMultiplier)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#inputOdometerMultiplier").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#inputOdometerMultiplier").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
//validate difference
|
||||||
|
if (vehicleOdometerDifference.trim() == '' || isNaN(vehicleOdometerDifference)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#inputOdometerDifference").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#inputOdometerDifference").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -78,7 +104,13 @@ function saveVehicle(isEdit) {
|
|||||||
licensePlate: vehicleLicensePlate,
|
licensePlate: vehicleLicensePlate,
|
||||||
isElectric: vehicleIsElectric,
|
isElectric: vehicleIsElectric,
|
||||||
tags: vehicleTags,
|
tags: vehicleTags,
|
||||||
useHours: vehicleUseHours
|
useHours: vehicleUseHours,
|
||||||
|
extraFields: extraFields.extraFields,
|
||||||
|
purchaseDate: vehiclePurchaseDate,
|
||||||
|
soldDate: vehicleSoldDate,
|
||||||
|
hasOdometerAdjustment: vehicleHasOdometerAdjustment,
|
||||||
|
odometerMultiplier: vehicleOdometerMultiplier,
|
||||||
|
odometerDifference: vehicleOdometerDifference
|
||||||
}, function (data) {
|
}, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
if (!isEdit) {
|
if (!isEdit) {
|
||||||
@@ -96,6 +128,14 @@ function saveVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function toggleOdometerAdjustment() {
|
||||||
|
var isChecked = $("#inputHasOdometerAdjustment").is(':checked');
|
||||||
|
if (isChecked) {
|
||||||
|
$("#odometerAdjustments").collapse('show');
|
||||||
|
} else {
|
||||||
|
$("#odometerAdjustments").collapse('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
function uploadFileAsync(event) {
|
function uploadFileAsync(event) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("file", event.files[0]);
|
formData.append("file", event.files[0]);
|
||||||
@@ -276,9 +316,23 @@ function updateAggregateLabels() {
|
|||||||
//Sum
|
//Sum
|
||||||
var sumLabel = $("[data-aggregate-type='sum']");
|
var sumLabel = $("[data-aggregate-type='sum']");
|
||||||
if (sumLabel.length > 0) {
|
if (sumLabel.length > 0) {
|
||||||
var newSum = $("[data-record-type='cost']").parent(":not('.override-hide')").children("[data-record-type='cost']").toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b,) => a + b).toFixed(2);
|
var labelsToSum = $("[data-record-type='cost']").parent(":not('.override-hide')").children("[data-record-type='cost']").toArray();
|
||||||
|
var newSum = 0;
|
||||||
|
if (labelsToSum.length > 0) {
|
||||||
|
newSum = labelsToSum.map(x => globalParseFloat(x.textContent)).reduce((a, b,) => a + b).toFixed(2);
|
||||||
|
}
|
||||||
sumLabel.text(`${sumLabel.text().split(':')[0]}: ${getGlobalConfig().currencySymbol}${newSum}`)
|
sumLabel.text(`${sumLabel.text().split(':')[0]}: ${getGlobalConfig().currencySymbol}${newSum}`)
|
||||||
}
|
}
|
||||||
|
//Sum Distance
|
||||||
|
var sumDistanceLabel = $("[data-aggregate-type='sum-distance']");
|
||||||
|
if (sumDistanceLabel.length > 0) {
|
||||||
|
var distanceLabelsToSum = $("[data-record-type='distance']").parent(":not('.override-hide')").children("[data-record-type='distance']").toArray();
|
||||||
|
var newDistanceSum = 0;
|
||||||
|
if (distanceLabelsToSum.length > 0) {
|
||||||
|
newDistanceSum = distanceLabelsToSum.map(x => globalParseFloat(x.textContent)).reduce((a, b,) => a + b).toFixed(0);
|
||||||
|
}
|
||||||
|
sumDistanceLabel.text(`${sumDistanceLabel.text().split(':')[0]}: ${newDistanceSum}`)
|
||||||
|
}
|
||||||
//Count
|
//Count
|
||||||
var newCount = $("[data-record-type='cost']").parent(":not('.override-hide')").length;
|
var newCount = $("[data-record-type='cost']").parent(":not('.override-hide')").length;
|
||||||
var countLabel = $("[data-aggregate-type='count']");
|
var countLabel = $("[data-aggregate-type='count']");
|
||||||
@@ -394,10 +448,11 @@ function showBulkImportModal(mode) {
|
|||||||
function hideBulkImportModal() {
|
function hideBulkImportModal() {
|
||||||
$("#bulkImportModal").modal('hide');
|
$("#bulkImportModal").modal('hide');
|
||||||
}
|
}
|
||||||
function getAndValidateExtraFields() {
|
function getAndValidateExtraFields(isVehicle) {
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
var outputData = [];
|
var outputData = [];
|
||||||
$(".extra-field").map((index, elem) => {
|
var fieldName = isVehicle ? '#addVehicleModalContent .extra-field,#editVehicleModalContent .extra-field' : '.extra-field:not(#addVehicleModalContent .extra-field, #editVehicleModalContent .extra-field)';
|
||||||
|
$(`${fieldName}`).map((index, elem) => {
|
||||||
var extraFieldName = $(elem).children("label").text();
|
var extraFieldName = $(elem).children("label").text();
|
||||||
var extraFieldInput = $(elem).children("input");
|
var extraFieldInput = $(elem).children("input");
|
||||||
var extraFieldValue = extraFieldInput.val();
|
var extraFieldValue = extraFieldInput.val();
|
||||||
@@ -629,7 +684,7 @@ $(window).on('mousedown', function (e) {
|
|||||||
$(window).on('keydown', function (e) {
|
$(window).on('keydown', function (e) {
|
||||||
var userOnInput = $(e.target).is("input") || $(e.target).is("textarea");
|
var userOnInput = $(e.target).is("input") || $(e.target).is("textarea");
|
||||||
if (!userOnInput) {
|
if (!userOnInput) {
|
||||||
if (e.ctrlKey && e.which == 65) {
|
if ((e.ctrlKey || e.metaKey) && e.which == 65) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
selectAllRows();
|
selectAllRows();
|
||||||
@@ -648,7 +703,7 @@ function rangeMouseDown(e) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var contextMenuAction = $(e.target).is(".table-context-menu > li > .dropdown-item")
|
var contextMenuAction = $(e.target).is(".table-context-menu > li > .dropdown-item")
|
||||||
if (!e.ctrlKey && !contextMenuAction) {
|
if (!(e.ctrlKey || e.metaKey) && !contextMenuAction) {
|
||||||
clearSelectedRows();
|
clearSelectedRows();
|
||||||
}
|
}
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
@@ -663,6 +718,12 @@ function isRightClick(e) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function stopEvent() {
|
||||||
|
if (isDragging) {
|
||||||
|
isDragging = false;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
function rangeMouseUp(e) {
|
function rangeMouseUp(e) {
|
||||||
if ($(".table-context-menu").length > 0) {
|
if ($(".table-context-menu").length > 0) {
|
||||||
$(".table-context-menu").hide();
|
$(".table-context-menu").hide();
|
||||||
@@ -753,6 +814,11 @@ function determineContextMenuItems() {
|
|||||||
} else {
|
} else {
|
||||||
$(".context-menu-multiple").hide();
|
$(".context-menu-multiple").hide();
|
||||||
}
|
}
|
||||||
|
if (GetVehicleId().hasOdometerAdjustment) {
|
||||||
|
$(".context-menu-odometer-adjustment").show();
|
||||||
|
} else {
|
||||||
|
$(".context-menu-odometer-adjustment").hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function getMenuPosition(mouse, direction, scrollDir) {
|
function getMenuPosition(mouse, direction, scrollDir) {
|
||||||
var win = $(window)[direction](),
|
var win = $(window)[direction](),
|
||||||
@@ -766,7 +832,7 @@ function getMenuPosition(mouse, direction, scrollDir) {
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
function handleTableRowClick(e, callBack, rowId) {
|
function handleTableRowClick(e, callBack, rowId) {
|
||||||
if (!event.ctrlKey) {
|
if (!(event.ctrlKey || event.metaKey)) {
|
||||||
callBack(rowId);
|
callBack(rowId);
|
||||||
} else if (!$(e).hasClass('table-active')) {
|
} else if (!$(e).hasClass('table-active')) {
|
||||||
addToSelectedRows($(e).attr('data-rowId'));
|
addToSelectedRows($(e).attr('data-rowId'));
|
||||||
@@ -830,7 +896,7 @@ function replenishSupplies() {
|
|||||||
var quantitybeforeRepl = globalParseFloat($('#supplyRecordQuantity').val());
|
var quantitybeforeRepl = globalParseFloat($('#supplyRecordQuantity').val());
|
||||||
if (isNaN(replquantity) || (replcost.trim() != '' && isNaN(parsedReplCost))) {
|
if (isNaN(replquantity) || (replcost.trim() != '' && isNaN(parsedReplCost))) {
|
||||||
Swal.showValidationMessage(`Please enter a valid quantity and cost`);
|
Swal.showValidationMessage(`Please enter a valid quantity and cost`);
|
||||||
} else if (replcost.trim() == '' && (isNaN(quantitybeforeRepl) || quantitybeforeRepl == 0)){
|
} else if (replcost.trim() == '' && (isNaN(quantitybeforeRepl) || quantitybeforeRepl == 0)) {
|
||||||
Swal.showValidationMessage(`Unable to use unit cost calculation, please provide cost`);
|
Swal.showValidationMessage(`Unable to use unit cost calculation, please provide cost`);
|
||||||
}
|
}
|
||||||
return { replquantity, replcost, parsedReplCost }
|
return { replquantity, replcost, parsedReplCost }
|
||||||
@@ -861,4 +927,87 @@ function replenishSupplies() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
function showTableColumns(e, tabName) {
|
||||||
|
//logic for extra field since we dont hardcode the data-column type
|
||||||
|
var showColumn = $(e).is(':checked');
|
||||||
|
var columnName = $(e).attr('data-column-toggle');
|
||||||
|
if (showColumn) {
|
||||||
|
$(`[data-column='${columnName}']`).show();
|
||||||
|
} else {
|
||||||
|
$(`[data-column='${columnName}']`).hide();
|
||||||
|
}
|
||||||
|
saveUserColumnPreferences(tabName);
|
||||||
|
}
|
||||||
|
function searchTableRows(tabName) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Search Records',
|
||||||
|
html: `
|
||||||
|
<input type="text" id="inputSearch" class="swal2-input" placeholder="Keyword(case sensitive)">
|
||||||
|
`,
|
||||||
|
confirmButtonText: 'Search',
|
||||||
|
focusConfirm: false,
|
||||||
|
preConfirm: () => {
|
||||||
|
const searchString = $("#inputSearch").val();
|
||||||
|
return { searchString }
|
||||||
|
},
|
||||||
|
}).then(function (result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
var rowData = $(`#${tabName} table tbody tr`);
|
||||||
|
var filteredRows = $(`#${tabName} table tbody tr td:contains('${result.value.searchString}')`).parent();
|
||||||
|
var splitSearchString = result.value.searchString.split('=');
|
||||||
|
if (result.value.searchString.includes('=') && splitSearchString.length == 2) {
|
||||||
|
//column specific search.
|
||||||
|
//get column index
|
||||||
|
var columns = $(`#${tabName} table th`).toArray().map(x => x.innerText);
|
||||||
|
var columnName = splitSearchString[0];
|
||||||
|
var colSearchString = splitSearchString[1];
|
||||||
|
var colIndex = columns.findIndex(x => x == columnName) + 1;
|
||||||
|
filteredRows = $(`#${tabName} table tbody tr td:nth-child(${colIndex}):contains('${colSearchString}')`).parent();
|
||||||
|
}
|
||||||
|
if (result.value.searchString.trim() == '') {
|
||||||
|
rowData.removeClass('override-hide');
|
||||||
|
} else {
|
||||||
|
rowData.addClass('override-hide');
|
||||||
|
filteredRows.removeClass('override-hide');
|
||||||
|
}
|
||||||
|
$(".tagfilter.bg-primary").addClass('bg-secondary').removeClass('bg-primary');
|
||||||
|
updateAggregateLabels();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function loadUserColumnPreferences(columns) {
|
||||||
|
if (columns.length == 0) {
|
||||||
|
//user has no preference saved, reset to default
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//uncheck all columns
|
||||||
|
$(".col-visible-toggle").prop("checked", false);
|
||||||
|
//hide all columns
|
||||||
|
$('[data-column]').hide();
|
||||||
|
//toggle visibility of each column
|
||||||
|
columns.map(x => {
|
||||||
|
var defaultColumn = $(`[data-column-toggle='${x}'].col-visible-toggle`);
|
||||||
|
if (defaultColumn.length > 0) {
|
||||||
|
defaultColumn.prop("checked", true);
|
||||||
|
$(`[data-column='${x}']`).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function saveUserColumnPreferences(importMode) {
|
||||||
|
var visibleColumns = $('.col-visible-toggle:checked').map((index, elem) => $(elem).attr('data-column-toggle')).toArray();
|
||||||
|
var columnPreference = {
|
||||||
|
tab: importMode,
|
||||||
|
visibleColumns: visibleColumns
|
||||||
|
};
|
||||||
|
$.post('/Vehicle/SaveUserColumnPreferences', { columnPreference: columnPreference }, function (data) {
|
||||||
|
if (!data) {
|
||||||
|
errorToast(genericErrorMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function copyToClipboard(e) {
|
||||||
|
var textToCopy = e.textContent.trim();
|
||||||
|
navigator.clipboard.writeText(textToCopy);
|
||||||
|
successToast("Copied to Clipboard");
|
||||||
}
|
}
|
||||||
@@ -86,6 +86,8 @@ function getAndValidateUpgradeRecordValues() {
|
|||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var upgradeRecordId = getUpgradeRecordModelData().id;
|
var upgradeRecordId = getUpgradeRecordModelData().id;
|
||||||
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
||||||
|
//Odometer Adjustments
|
||||||
|
upgradeMileage = GetAdjustedOdometer(upgradeRecordId, upgradeMileage);
|
||||||
//validation
|
//validation
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
var extraFields = getAndValidateExtraFields();
|
var extraFields = getAndValidateExtraFields();
|
||||||
|
|||||||
@@ -215,6 +215,8 @@ function editVehicle(vehicleId) {
|
|||||||
if (data) {
|
if (data) {
|
||||||
$("#editVehicleModalContent").html(data);
|
$("#editVehicleModalContent").html(data);
|
||||||
initTagSelector($("#inputTag"), true);
|
initTagSelector($("#inputTag"), true);
|
||||||
|
initDatePicker($('#inputPurchaseDate'));
|
||||||
|
initDatePicker($('#inputSoldDate'));
|
||||||
$('#editVehicleModal').modal('show');
|
$('#editVehicleModal').modal('show');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -438,4 +440,118 @@ function getAndValidateGenericRecordValues() {
|
|||||||
tags: genericTags
|
tags: genericTags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
function getRecordsDeltaStats(recordIds) {
|
||||||
|
if (recordIds.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var odometerReadings = [];
|
||||||
|
var dateReadings = [];
|
||||||
|
var costReadings = [];
|
||||||
|
//get all of the odometer readings
|
||||||
|
recordIds.map(x => {
|
||||||
|
var odometerReading = parseInt($(`tr[data-rowId='${x}'] td[data-column='odometer']`).text());
|
||||||
|
if (!isNaN(odometerReading)) {
|
||||||
|
odometerReadings.push(odometerReading);
|
||||||
|
}
|
||||||
|
var dateReading = parseInt($(`tr[data-rowId=${x}] td[data-column='date']`).attr('data-date'));
|
||||||
|
if (!isNaN(dateReading)) {
|
||||||
|
dateReadings.push(dateReading);
|
||||||
|
}
|
||||||
|
var costReading = globalParseFloat($(`tr[data-rowId='${x}'] td[data-column='cost']`).text());
|
||||||
|
if (costReading > 0) {
|
||||||
|
costReadings.push(costReading);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//get max stats
|
||||||
|
var maxOdo = odometerReadings.length > 0 ? odometerReadings.reduce((a, b) => a > b ? a : b) : 0;
|
||||||
|
var maxDate = dateReadings.length > 0 ? dateReadings.reduce((a, b) => a > b ? a : b) : 0;
|
||||||
|
//get min stats
|
||||||
|
var minOdo = odometerReadings.length > 0 ? odometerReadings.reduce((a, b) => a < b ? a : b) : 0;
|
||||||
|
var minDate = dateReadings.length > 0 ? dateReadings.reduce((a, b) => a < b ? a : b) : 0;
|
||||||
|
//get sum of costs
|
||||||
|
var costSum = costReadings.length > 0 ? costReadings.reduce((a, b) => a + b) : 0;
|
||||||
|
var diffOdo = maxOdo - minOdo;
|
||||||
|
var diffDate = maxDate - minDate;
|
||||||
|
var divisibleCount = recordIds.length - 1;
|
||||||
|
var averageOdo = diffOdo > 0 ? (diffOdo / divisibleCount).toFixed(2) : 0;
|
||||||
|
var averageDays = diffDate > 0 ? Math.floor((diffDate / divisibleCount) / 8.64e7) : 0;
|
||||||
|
var averageSum = costSum > 0 ? (costSum / recordIds.length).toFixed(2) : 0;
|
||||||
|
costSum = costSum.toFixed(2);
|
||||||
|
Swal.fire({
|
||||||
|
title: "Record Statistics",
|
||||||
|
html: `<p>Average Distance Traveled between Records: ${averageOdo}</p>
|
||||||
|
<br />
|
||||||
|
<p>Average Days between Records: ${averageDays}</p>
|
||||||
|
<br />
|
||||||
|
<p>Total Cost: ${getGlobalConfig().currencySymbol} ${costSum}</p>
|
||||||
|
<br />
|
||||||
|
<p>Average Cost: ${getGlobalConfig().currencySymbol} ${averageSum}</p>`
|
||||||
|
,
|
||||||
|
icon: "info"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function GetAdjustedOdometer(id, odometerInput) {
|
||||||
|
//if editing an existing record or vehicle does not have odometer adjustment or input is NaN then just return the original input.
|
||||||
|
if (id > 0 || !GetVehicleId().hasOdometerAdjustment || isNaN(odometerInput)) {
|
||||||
|
return odometerInput;
|
||||||
|
}
|
||||||
|
//apply odometer adjustments first.
|
||||||
|
var adjustedOdometer = parseInt(odometerInput) + parseInt(GetVehicleId().odometerDifference);
|
||||||
|
//apply odometer multiplier.
|
||||||
|
adjustedOdometer *= globalParseFloat(GetVehicleId().odometerMultiplier);
|
||||||
|
return adjustedOdometer.toFixed(0);
|
||||||
|
}
|
||||||
|
function adjustRecordsOdometer(ids, source) {
|
||||||
|
if (ids.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#workAroundInput").show();
|
||||||
|
var friendlySource = "";
|
||||||
|
var refreshDataCallBack;
|
||||||
|
var recordVerbiage = ids.length > 1 ? `these ${ids.length} records` : "this record";
|
||||||
|
switch (source) {
|
||||||
|
case "ServiceRecord":
|
||||||
|
friendlySource = "Service Records";
|
||||||
|
refreshDataCallBack = getVehicleServiceRecords;
|
||||||
|
break;
|
||||||
|
case "RepairRecord":
|
||||||
|
friendlySource = "Repairs";
|
||||||
|
refreshDataCallBack = getVehicleCollisionRecords;
|
||||||
|
break;
|
||||||
|
case "UpgradeRecord":
|
||||||
|
friendlySource = "Upgrades";
|
||||||
|
refreshDataCallBack = getVehicleUpgradeRecords;
|
||||||
|
break;
|
||||||
|
case "OdometerRecord":
|
||||||
|
friendlySource = "Odometer Records";
|
||||||
|
refreshDataCallBack = getVehicleOdometerRecords;
|
||||||
|
break;
|
||||||
|
case "GasRecord":
|
||||||
|
friendlySource = "Fuel Records";
|
||||||
|
refreshDataCallBack = getVehicleGasRecords;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: "Adjust Odometer?",
|
||||||
|
text: `Apply Odometer Adjustments to ${recordVerbiage}?`,
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: "Adjust",
|
||||||
|
confirmButtonColor: "#dc3545"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
$.post('/Vehicle/AdjustRecordsOdometer', { recordIds: ids, vehicleId: GetVehicleId().vehicleId, importMode: source }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast(`${ids.length} Record(s) Updated`);
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
refreshDataCallBack(vehicleId);
|
||||||
|
} else {
|
||||||
|
errorToast(genericErrorMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$("#workAroundInput").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -299,8 +299,7 @@
|
|||||||
if (self.$element.attr('disabled')) {
|
if (self.$element.attr('disabled')) {
|
||||||
self.$input.attr('disabled', 'disabled');
|
self.$input.attr('disabled', 'disabled');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.which) {
|
switch (event.which) {
|
||||||
// BACKSPACE
|
// BACKSPACE
|
||||||
case 8:
|
case 8:
|
||||||
@@ -339,7 +338,38 @@
|
|||||||
$nextTag.after($inputWrapper);
|
$nextTag.after($inputWrapper);
|
||||||
$input.focus();
|
$input.focus();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
//COPY EVENT
|
||||||
|
case 67:
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
navigator.clipboard.writeText(self.itemsArray.join(" "));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
//PASTE EVENT
|
||||||
|
case 86:
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
setTimeout(function () {
|
||||||
|
var pastedString = $input.val();
|
||||||
|
//clear pasted string.
|
||||||
|
$input.val('');
|
||||||
|
//process input one by one.
|
||||||
|
if (pastedString.length > 0) {
|
||||||
|
var tagsToAdd = pastedString.split(" ");
|
||||||
|
tagsToAdd.forEach(x => {
|
||||||
|
self.add(x);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
//ENTER EVENT
|
||||||
|
case 13:
|
||||||
|
case 32:
|
||||||
|
var tagToAdd = $input.val();
|
||||||
|
$input.val('');
|
||||||
|
self.add(tagToAdd);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@@ -351,61 +381,18 @@
|
|||||||
$input.attr('size', Math.max(this.inputSize, size));
|
$input.attr('size', Math.max(this.inputSize, size));
|
||||||
}, self));
|
}, self));
|
||||||
|
|
||||||
self.$container.on('input', 'input', $.proxy(function (event) {
|
self.$container.on('input', 'input', $.proxy(function(event) {
|
||||||
if (event.originalEvent.data == undefined) {
|
var $input = $(event.target);
|
||||||
var $input = $(event.target);
|
//check if the previous inserted value was a space.
|
||||||
var text = $input.val(),
|
var text = $input.val();
|
||||||
maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
|
if (text.length > 0) {
|
||||||
if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
|
var lastChar = text.charAt(text.length - 1);
|
||||||
//check if confirm keys are in input and then replace them.
|
if (lastChar == " ") {
|
||||||
text = text.replace(String.fromCharCode(event.which), "")
|
text = text.replace(" ", "");
|
||||||
// Only attempt to add a tag if there is data in the field
|
self.add(text);
|
||||||
if (text.length !== 0) {
|
$input.val('');
|
||||||
self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
|
}
|
||||||
$input.val('');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If the field is empty, let the event triggered fire as usual
|
|
||||||
if (self.options.cancelConfirmKeysOnEmpty === false) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var textLength = $input.val().length,
|
|
||||||
wordSpace = Math.ceil(textLength / 5),
|
|
||||||
size = textLength + wordSpace + 1;
|
|
||||||
$input.attr('size', Math.max(this.inputSize, size));
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.$container.on('keypress', 'input', $.proxy(function(event) {
|
|
||||||
var $input = $(event.target);
|
|
||||||
|
|
||||||
if (self.$element.attr('disabled')) {
|
|
||||||
self.$input.attr('disabled', 'disabled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var text = $input.val(),
|
|
||||||
maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
|
|
||||||
if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
|
|
||||||
//check if confirm keys are in input and then replace them.
|
|
||||||
text = text.replace(String.fromCharCode(event.which), "")
|
|
||||||
// Only attempt to add a tag if there is data in the field
|
|
||||||
if (text.length !== 0) {
|
|
||||||
self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
|
|
||||||
$input.val('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the field is empty, let the event triggered fire as usual
|
|
||||||
if (self.options.cancelConfirmKeysOnEmpty === false) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset internal input's size
|
|
||||||
var textLength = $input.val().length,
|
|
||||||
wordSpace = Math.ceil(textLength / 5),
|
|
||||||
size = textLength + wordSpace + 1;
|
|
||||||
$input.attr('size', Math.max(this.inputSize, size));
|
|
||||||
}, self));
|
}, self));
|
||||||
|
|
||||||
// Remove icon clicked
|
// Remove icon clicked
|
||||||
@@ -562,33 +549,4 @@
|
|||||||
}
|
}
|
||||||
return (iCaretPos);
|
return (iCaretPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns boolean indicates whether user has pressed an expected key combination.
|
|
||||||
* @param object keyPressEvent: JavaScript event object, refer
|
|
||||||
* http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
|
|
||||||
* @param object lookupList: expected key combinations, as in:
|
|
||||||
* [13, {which: 188, shiftKey: true}]
|
|
||||||
*/
|
|
||||||
function keyCombinationInList(keyPressEvent, lookupList) {
|
|
||||||
var found = false;
|
|
||||||
$.each(lookupList, function (index, keyCombination) {
|
|
||||||
if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
|
|
||||||
found = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyPressEvent.which === keyCombination.which) {
|
|
||||||
var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
|
|
||||||
shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
|
|
||||||
ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
|
|
||||||
if (alt && shift && ctrl) {
|
|
||||||
found = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
})(window.jQuery);
|
})(window.jQuery);
|
||||||
|
|||||||
Reference in New Issue
Block a user