Compare commits

..

77 Commits

Author SHA1 Message Date
Hargata Softworks
6e4e2795b6 Merge pull request #406 from hargata/Hargata/multi.gas.edit
fixed ui bug
2024-03-16 12:24:55 -06:00
DESKTOP-GENO133\IvanPlex
d4e51b714d fixed ui bug 2024-03-16 12:23:46 -06:00
Hargata Softworks
6a164dc60b Merge pull request #405 from hargata/Hargata/multi.gas.edit
Added functionality to edit multiple gas records.
2024-03-16 12:11:52 -06:00
DESKTOP-GENO133\IvanPlex
ce602dcf66 Added functionality to edit multiple gas records. 2024-03-16 12:10:40 -06:00
Hargata Softworks
2facb1ab46 Merge pull request #402 from hargata/Hargata/further.improvements
More enhancements
2024-03-16 10:04:03 -06:00
DESKTOP-GENO133\IvanPlex
3c7d575c85 fixes and ensure that users no longer have to hard refresh their sites. 2024-03-16 10:01:48 -06:00
DESKTOP-GENO133\IvanPlex
9635c3c2c5 fixed bug where translations are not being backed up. 2024-03-16 09:44:27 -06:00
DESKTOP-GENO133\IvanPlex
72427fc19d fixed average calculation 2024-03-15 17:15:05 -06:00
DESKTOP-GENO133\IvanPlex
0bbd3c5491 fixed spelling 2024-03-15 17:09:51 -06:00
DESKTOP-GENO133\IvanPlex
871de4e75a updated translation 2024-03-15 17:07:01 -06:00
DESKTOP-GENO133\IvanPlex
bf984f280e added feature to make odometer adjustments a piece of cake. 2024-03-15 16:48:23 -06:00
DESKTOP-GENO133\IvanPlex
2b8f3cf13a properly capitalized web hook descriptions. 2024-03-15 16:21:41 -06:00
Hargata Softworks
1630a5c9ec Merge pull request #401 from hargata/Hargata/webhook
added web hooks
2024-03-15 15:12:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f17faa33f4 fixed punctuation 2024-03-15 15:09:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c245b848a0 reminder hooks 2024-03-15 15:06:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0ead9112c6 added vehicle deets 2024-03-15 15:01:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
44da393369 added web hooks 2024-03-15 14:59:30 -06:00
Hargata Softworks
5ae1628b7c Merge pull request #399 from hargata/Hargata/odo.modifier
Odometer Modifier
2024-03-15 14:05:41 -06:00
DESKTOP-GENO133\IvanPlex
59511d9ddd Updated version number 2024-03-15 12:49:34 -06:00
DESKTOP-GENO133\IvanPlex
82b0fba99a added functionality to modify odometer value when adding new odometer records. 2024-03-15 12:49:02 -06:00
DESKTOP-GENO133\IvanPlex
618107e515 added fields for odometer adjustments. 2024-03-15 10:16:28 -06:00
DESKTOP-GENO133\IvanPlex
35f931adf2 additional fields for vehicle level odometer settings. 2024-03-14 23:17:48 -06:00
Hargata Softworks
4a1e41cd54 Merge pull request #393 from hargata/Hargata/multi.odo.edit
Added functionality to edit multiple odometer records.
2024-03-13 10:18:13 -06:00
DESKTOP-T0O5CDB\DESK-555BD
888ec5cbbe Added functionality to edit multiple odometer records. 2024-03-13 10:16:04 -06:00
Hargata Softworks
f8f044d0cc Merge pull request #391 from hargata/Hargata/odo.changes
width issue
2024-03-11 11:42:26 -06:00
DESKTOP-GENO133\IvanPlex
36148c5539 width issue 2024-03-11 11:41:53 -06:00
Hargata Softworks
030dde0d64 Merge pull request #390 from hargata/Hargata/odo.changes
fixed admin panel text and button spacing.
2024-03-11 11:40:28 -06:00
DESKTOP-GENO133\IvanPlex
99faa951f9 fixed admin panel text and button spacing. 2024-03-11 11:39:56 -06:00
Hargata Softworks
7fb3a80ea3 Merge pull request #389 from hargata/Hargata/odo.changes
added support for meta key for MacOS users
2024-03-11 10:37:40 -06:00
DESKTOP-GENO133\IvanPlex
f277048aa9 added method to force recalculate distances 2024-03-11 10:34:42 -06:00
DESKTOP-GENO133\IvanPlex
34e3b8e145 added support for meta key for MacOS users 2024-03-11 10:01:24 -06:00
Hargata Softworks
91e8526b8e Merge pull request #388 from hargata/Hargata/odo.changes
removed logic that can cause bad data.
2024-03-10 14:19:54 -06:00
DESKTOP-GENO133\IvanPlex
926188ef64 removed logic that can cause bad data. 2024-03-10 14:18:24 -06:00
Hargata Softworks
1b5fc6729e Merge pull request #387 from hargata/Hargata/odo.changes
Odometer Changes
2024-03-10 14:13:35 -06:00
DESKTOP-GENO133\IvanPlex
3ac53ea144 added method to calculate distance. 2024-03-10 11:59:38 -06:00
DESKTOP-GENO133\IvanPlex
4dc3b4f741 fixed csv import 2024-03-10 09:05:15 -06:00
DESKTOP-GENO133\IvanPlex
7c1e6bd45c made API endpoints copyable 2024-03-10 08:56:41 -06:00
DESKTOP-GENO133\IvanPlex
00b3145e79 display distance traveled. 2024-03-10 08:02:44 -06:00
DESKTOP-GENO133\IvanPlex
def9a7770f odometer to trips improvement. 2024-03-10 06:33:39 -06:00
Hargata Softworks
5ba0bd5771 Merge pull request #381 from hargata/Hargata/fix.print
7z
2024-03-08 16:19:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
adc2de07f7 7z 2024-03-08 16:19:36 -07:00
Hargata Softworks
e12f528635 Merge pull request #380 from hargata/Hargata/fix.print
fixed the date column.
2024-03-08 16:17:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c96ac53b91 fixed the date column. 2024-03-08 16:17:20 -07:00
Hargata Softworks
8c15bd5534 Merge pull request #378 from hargata/Hargata/fix.print
Improved Attachment Export
2024-03-08 11:49:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
90755d68b3 Improved Attachment Export 2024-03-08 11:49:30 -07:00
Hargata Softworks
94dfe9a0bb Merge pull request #377 from hargata/Hargata/fix.print
Added flex shrink
2024-03-08 11:13:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9dbcf71856 Added flex shrink 2024-03-08 11:11:37 -07:00
Hargata Softworks
728fc3593f Merge pull request #374 from hargata/Hargata/fix.print
Fixed black border issue when printing
2024-03-07 22:49:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1347585af2 Fixed black border issue when printing 2024-03-07 22:49:11 -07:00
Hargata Softworks
920de489be Merge pull request #372 from hargata/Hargata/delta.stats
Added MOTD.
2024-03-07 08:46:14 -07:00
DESKTOP-GENO133\IvanPlex
19cb964543 Added MOTD. 2024-03-07 08:44:58 -07:00
Hargata Softworks
eb03979ad8 Merge pull request #369 from hargata/Hargata/delta.stats
Added Total and Average Cost
2024-03-06 07:44:28 -07:00
DESKTOP-GENO133\IvanPlex
ebe871b82a reduced operations 2024-03-06 07:42:46 -07:00
DESKTOP-GENO133\IvanPlex
04a103fdc0 column specific search 2024-03-06 07:40:18 -07:00
DESKTOP-GENO133\IvanPlex
d0f80d4150 Added Total and Average Cost 2024-03-05 08:43:10 -07:00
Hargata Softworks
48c388dea7 Merge pull request #368 from redge76/Sample_postgreSQL
Sample postgreSQL configuration
2024-03-04 17:13:02 -07:00
Redge
d05f8b1f84 Sample postgrSQL connection stringin file .env
add a sample connection string to enable postgrSQL database backend
2024-03-05 01:09:10 +01:00
Redge
167d6e607e Sample docker-compose.postgresql.yml 2024-03-05 01:05:24 +01:00
Hargata Softworks
8755c6379a Merge pull request #367 from hargata/Hargata/record.delta.stats
Hargata/record.delta.stats
2024-03-04 16:20:24 -07:00
DESKTOP-GENO133\IvanPlex
1e2bd0ea4b Allow users to set their own reminder urgency threshold 2024-03-04 16:18:09 -07:00
DESKTOP-GENO133\IvanPlex
2cf93cf669 Added setting to hide sold vehicles. 2024-03-04 13:37:07 -07:00
Hargata Softworks
a18b81d52e Merge pull request #363 from hargata/Hargata/record.delta.stats
Hargata/record.delta.stats
2024-03-03 21:10:14 -07:00
DESKTOP-GENO133\IvanPlex
604c98a031 allowed users to configure their own file extensions. 2024-03-03 19:32:20 -07:00
DESKTOP-GENO133\IvanPlex
940f48b816 Updated version number 2024-03-03 09:20:02 -07:00
DESKTOP-GENO133\IvanPlex
f81545178d Added statistics between records. 2024-03-03 09:09:42 -07:00
Hargata Softworks
a87b599bdf Merge pull request #362 from hargata/Hargata/persist.columns
null check for column preferences
2024-03-02 19:13:11 -07:00
DESKTOP-GENO133\IvanPlex
2c45521fb4 null check for column preferences 2024-03-02 19:11:21 -07:00
Hargata Softworks
0870387995 Merge pull request #361 from hargata/Hargata/persist.columns
Allows users to inject root user credentials as environment variables.
2024-03-02 07:53:48 -07:00
DESKTOP-GENO133\IvanPlex
be281c78ff Allows users to inject root user credentials as environment variables. 2024-03-02 07:51:37 -07:00
Hargata Softworks
87c54335db Merge pull request #360 from hargata/Hargata/persist.columns
authenticate root user via configHelper
2024-03-02 07:13:43 -07:00
DESKTOP-GENO133\IvanPlex
12e6c1677b null checks 2024-03-02 07:13:13 -07:00
DESKTOP-GENO133\IvanPlex
f69b789346 authenticate root user via configHelper 2024-03-02 07:11:54 -07:00
Hargata Softworks
9f05295f18 Merge pull request #359 from hargata/Hargata/persist.columns
revamped column check toggle behavior
2024-03-02 06:50:52 -07:00
DESKTOP-GENO133\IvanPlex
bf14e4c8c0 added functonality to persist column visibility 2024-03-02 06:49:40 -07:00
Hargata Softworks
bd4db09637 Merge pull request #357 from hargata/Hargata/responsive.table.columns
allow table columns to grow as needed.
2024-03-02 05:48:23 -07:00
DESKTOP-GENO133\IvanPlex
4b5dcb1c7e revamped column check toggle behavior 2024-03-02 05:47:22 -07:00
DESKTOP-GENO133\IvanPlex
b7f1ac5e8f allow table columns to grow as needed. 2024-03-02 04:57:45 -07:00
68 changed files with 1634 additions and 377 deletions

4
.env
View File

@@ -7,3 +7,7 @@ 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;"

View File

@@ -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);
@@ -353,7 +360,12 @@ namespace CarCareTracker.Controllers
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))]
@@ -384,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);
@@ -466,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);

View File

@@ -73,8 +73,7 @@ namespace CarCareTracker.Controllers
//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);
} }
@@ -84,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()
@@ -101,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();

View File

@@ -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;
} }
@@ -127,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);
} }
@@ -151,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]
@@ -265,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))
@@ -460,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,
@@ -485,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,
@@ -500,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()
@@ -540,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,
@@ -564,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,
@@ -646,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,
@@ -656,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]
@@ -700,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]
@@ -711,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))]
@@ -734,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,
@@ -754,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]
@@ -786,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
@@ -811,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,
@@ -831,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]
@@ -863,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
@@ -934,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]
@@ -967,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
@@ -1004,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);
@@ -1132,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
@@ -1142,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
@@ -1152,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
@@ -1162,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
@@ -1172,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
@@ -1182,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
@@ -1221,7 +1327,8 @@ namespace CarCareTracker.Controllers
try try
{ {
vehicleHistory.DaysOwned = (DateTime.Parse(endDate) - DateTime.Parse(vehicleHistory.VehicleData.PurchaseDate)).Days.ToString("N0"); vehicleHistory.DaysOwned = (DateTime.Parse(endDate) - DateTime.Parse(vehicleHistory.VehicleData.PurchaseDate)).Days.ToString("N0");
} catch (Exception ex) }
catch (Exception ex)
{ {
_logger.LogError(ex.Message); _logger.LogError(ex.Message);
vehicleHistory.DaysOwned = string.Empty; vehicleHistory.DaysOwned = string.Empty;
@@ -1370,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);
} }
@@ -1411,7 +1518,7 @@ namespace CarCareTracker.Controllers
private int GetMinMileage(int vehicleId) private int GetMinMileage(int vehicleId)
{ {
var numbersArray = new List<int>(); var numbersArray = new List<int>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x=>x.Mileage != default); var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
if (serviceRecords.Any()) if (serviceRecords.Any())
{ {
numbersArray.Add(serviceRecords.Min(x => x.Mileage)); numbersArray.Add(serviceRecords.Min(x => x.Mileage));
@@ -1532,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]
@@ -1572,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
@@ -1597,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,
@@ -1617,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]
@@ -1649,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
@@ -1673,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]
@@ -1690,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;
} }
@@ -1810,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]
@@ -1844,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
@@ -1871,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]
@@ -1962,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,
@@ -2056,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)
{ {
@@ -2082,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)
@@ -2098,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,
@@ -2111,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
@@ -2212,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)
@@ -2250,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)
@@ -2324,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]
@@ -2448,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
} }
} }

View File

@@ -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,9 +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)]),
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]), EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
VisibleTabs = _config.GetSection("VisibleTabs").Get<List<ImportMode>>(), 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;

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,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)
@@ -412,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)

67
Logic/OdometerLogic.cs Normal file
View 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;
}
}
}

View File

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

View 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();
}
}

View File

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

View 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();
}
}

View File

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

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

View File

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

View File

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

View 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>();
}
}

View File

@@ -15,8 +15,11 @@
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 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";

View File

@@ -14,5 +14,14 @@
public bool UseHours { get; set; } = false; public bool UseHours { get; set; } = false;
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>(); 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";
} }
} }

View File

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

View File

@@ -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">
@@ -289,7 +289,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/latest</code> <code>/api/vehicle/odometerrecords/latest</code>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -303,7 +303,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/odometerrecords/add</code> <code>/api/vehicle/odometerrecords/add</code>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -314,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>

View File

@@ -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',

View File

@@ -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()">
@@ -119,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>

View File

@@ -28,21 +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-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)"> @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; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" /> <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)">
@if (!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; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
<div class="vehicle-sold-banner"><p class='display-6 mb-0'>@translator.Translate(userLanguage, "SOLD")</p></div> @if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
} {
<div class="card-body"> <div class="vehicle-sold-banner"><p class='display-6 mb-0'>@translator.Translate(userLanguage, "SOLD")</p></div>
<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> <div class="card-body">
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5> <h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>
<p class="card-text text-truncate">@vehicle.LicensePlate</p> <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%;">

View File

@@ -57,6 +57,10 @@
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableExtraFieldColumns" checked="@Model.UserConfig.EnableExtraFieldColumns"> <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> <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>
<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>
@@ -184,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>
@@ -205,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.4</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.
@@ -250,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);
@@ -292,6 +346,7 @@
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"), enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
enableShopSupplies: $("#enableShopSupplies").is(":checked"), enableShopSupplies: $("#enableShopSupplies").is(":checked"),
enableExtraFieldColumns: $("#enableExtraFieldColumns").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(),
@@ -347,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",
@@ -360,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.");
} }
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@
{ {
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList(); 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">
@@ -48,31 +49,31 @@
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='date' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Date" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='odometer' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Odometer" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='description' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Description" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='cost' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Cost" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='notes' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Notes" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div> </div>
</li> </li>
@@ -81,7 +82,7 @@
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" onChange="showTableColumns(this, true)" type="checkbox" id="@elementId"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div> </div>
</li> </li>
@@ -106,14 +107,14 @@
<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" data-column="date">@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" data-column="odometer">@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" data-column="description">@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" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<th scope="col" style='display:none;' class="col-2" data-column="@extraFieldColumn">@extraFieldColumn</th> <th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
} }
</tr> </tr>
</thead> </thead>
@@ -121,14 +122,14 @@
@foreach (CollisionRecord collisionRecord in Model) @foreach (CollisionRecord collisionRecord in Model)
{ {
<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)'> <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" data-column="date">@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" data-column="odometer">@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" data-column="description">@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-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<td class="col-2 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(collisionRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td> <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>
} }
@@ -156,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>
<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> </ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -46,6 +46,7 @@
{ {
extraFields = Model.GasRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList(); 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">
@@ -95,49 +96,49 @@
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='daterefueled' onChange="showTableColumns(this)" type="checkbox" id="chkCol_DateRefueled" checked> <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> <label class="form-check-label stretched-link" for="chkCol_DateRefueled">@translator.Translate(userLanguage, "Date Refueled")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='odometer' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Odometer" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='delta' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Delta" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Delta">@translator.Translate(userLanguage, "Delta")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='consumption' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Consumption" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Consumption">@translator.Translate(userLanguage, "Consumption")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='fueleconomy' onChange="showTableColumns(this)" type="checkbox" id="chkCol_FuelEconomy" checked> <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> <label class="form-check-label stretched-link" for="chkCol_FuelEconomy">@translator.Translate(userLanguage, "Fuel Economy")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='cost' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Cost" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='unitcost' onChange="showTableColumns(this)" type="checkbox" id="chkCol_UnitCost" checked> <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> <label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='notes' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Notes"> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div> </div>
</li> </li>
@@ -146,7 +147,7 @@
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" onChange="showTableColumns(this, true)" type="checkbox" id="@elementId"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div> </div>
</li> </li>
@@ -170,17 +171,17 @@
<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" data-column="daterefueled">@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" data-column="odometer">@($"{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" data-column="delta" 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-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-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-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-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" data-column="cost" 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" data-column="unitcost" 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" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</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) @foreach (string extraFieldColumn in extraFields)
{ {
<th scope="col" style='display:none;' class="col-2" data-column="@extraFieldColumn">@extraFieldColumn</th> <th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
} }
</tr> </tr>
</thead> </thead>
@@ -188,17 +189,17 @@
@foreach (GasRecordViewModel gasRecord in Model.GasRecords) @foreach (GasRecordViewModel gasRecord in Model.GasRecords)
{ {
<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)'> <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" data-column="daterefueled">@gasRecord.Date</td> <td class="col-2 flex-grow-1" data-column="daterefueled">@gasRecord.Date</td>
<td class="col-2" data-column="odometer" 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" data-column="delta">@(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-column="consumption" 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-column="fueleconomy" 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-column="cost" 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-column="unitcost" 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 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</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) @foreach (string extraFieldColumn in extraFields)
{ {
<td class="col-2 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(gasRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td> <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>
} }
@@ -218,11 +219,17 @@
<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))
{ {

View File

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

View 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>

View File

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

View File

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

View File

@@ -12,12 +12,14 @@
{ {
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList(); 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>
@@ -47,19 +49,31 @@
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='date' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Date" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='odometer' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Odometer" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='notes' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Notes" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div> </div>
</li> </li>
@@ -68,7 +82,7 @@
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" onChange="showTableColumns(this, true)" type="checkbox" id="@elementId"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div> </div>
</li> </li>
@@ -93,12 +107,14 @@
<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" data-column="date">@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" data-column="odometer">@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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<th scope="col" style='display:none;' class="col-2" data-column="@extraFieldColumn">@extraFieldColumn</th> <th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
} }
</tr> </tr>
</thead> </thead>
@@ -106,12 +122,14 @@
@foreach (OdometerRecord odometerRecord in Model) @foreach (OdometerRecord odometerRecord in Model)
{ {
<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)'> <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" data-column="date">@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-column="odometer" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<td class="col-2 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(odometerRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td> <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>
} }
@@ -131,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>
<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> </ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View 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>

View File

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

View File

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

View File

@@ -12,6 +12,7 @@
{ {
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList(); 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">
@@ -48,31 +49,31 @@
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='date' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Date" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='odometer' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Odometer" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='description' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Description" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='cost' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Cost" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='notes' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Notes" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div> </div>
</li> </li>
@@ -81,7 +82,7 @@
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" onChange="showTableColumns(this, true)" type="checkbox" id="@elementId"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div> </div>
</li> </li>
@@ -106,14 +107,14 @@
<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" data-column="date">@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" data-column="odometer">@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" data-column="description">@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" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<th scope="col" style='display:none;' class="col-2" data-column="@extraFieldColumn">@extraFieldColumn</th> <th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
} }
</tr> </tr>
</thead> </thead>
@@ -121,14 +122,14 @@
@foreach (ServiceRecord serviceRecord in Model) @foreach (ServiceRecord serviceRecord in Model)
{ {
<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)'> <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" data-column="date">@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" data-column="odometer">@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" data-column="description">@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-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<td class="col-2 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(serviceRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td> <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>
} }
@@ -156,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>
<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> </ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

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

View File

@@ -12,6 +12,7 @@
{ {
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList(); 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">
@@ -48,43 +49,43 @@
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='date' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Date" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='partnumber' onChange="showTableColumns(this)" type="checkbox" id="chkCol_PartNumber" checked> <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> <label class="form-check-label stretched-link" for="chkCol_PartNumber">@translator.Translate(userLanguage, "Part Number")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='supplier' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Supplier" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Supplier">@translator.Translate(userLanguage, "Supplier")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='description' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Description" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='quantity' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Quantity" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Quantity">@translator.Translate(userLanguage, "Quantity")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='cost' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Cost" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='notes' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Notes" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div> </div>
</li> </li>
@@ -93,7 +94,7 @@
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" onChange="showTableColumns(this, true)" type="checkbox" id="@elementId"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div> </div>
</li> </li>
@@ -118,16 +119,16 @@
<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" data-column="date">@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" data-column="partnumber">@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" data-column="supplier">@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" data-column="description">@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" data-column="quantity" 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" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<th scope="col" style='display:none;' class="col-2" data-column="@extraFieldColumn">@extraFieldColumn</th> <th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
} }
</tr> </tr>
</thead> </thead>
@@ -135,16 +136,16 @@
@foreach (SupplyRecord supplyRecord in Model) @foreach (SupplyRecord supplyRecord in Model)
{ {
<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)'> <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" data-column="date">@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" data-column="partnumber">@supplyRecord.PartNumber</td> <td class="col-2 flex-grow-1 flex-shrink-1" data-column="partnumber">@supplyRecord.PartNumber</td>
<td class="col-2" data-column="supplier">@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" data-column="description">@supplyRecord.Description</td> <td class="col-2 flex-grow-1 col-xl-3" data-column="description">@supplyRecord.Description</td>
<td class="col-1" data-column="quantity">@supplyRecord.Quantity</td> <td class="col-1 flex-grow-1 flex-shrink-1" data-column="quantity">@supplyRecord.Quantity</td>
<td class="col-1" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<td class="col-2 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(supplyRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td> <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>
} }
@@ -168,3 +169,7 @@
<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)
}

View File

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

View File

@@ -12,6 +12,7 @@
{ {
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList(); 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">
@@ -48,25 +49,25 @@
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='date' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Date" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='description' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Description" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='cost' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Cost" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='notes' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Notes" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div> </div>
</li> </li>
@@ -75,7 +76,7 @@
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" onChange="showTableColumns(this, true)" type="checkbox" id="@elementId"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div> </div>
</li> </li>
@@ -100,13 +101,13 @@
<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" data-column="date">@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" data-column="description">@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" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<th scope="col" style='display:none;' class="col-2" data-column="@extraFieldColumn">@extraFieldColumn</th> <th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
} }
</tr> </tr>
</thead> </thead>
@@ -114,13 +115,13 @@
@foreach (TaxRecord taxRecord in Model) @foreach (TaxRecord taxRecord in Model)
{ {
<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)'> <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" data-column="date">@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" data-column="description">@taxRecord.Description</td> <td class="col-4 flex-grow-1 col-xl-6" data-column="description">@taxRecord.Description</td>
<td class="col-2" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<td class="col-2 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(taxRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td> <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>
} }
@@ -144,3 +145,7 @@
<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)
}

View File

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

View File

@@ -12,6 +12,7 @@
{ {
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList(); 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">
@@ -48,31 +49,31 @@
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='date' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Date" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='odometer' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Odometer" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='description' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Description" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='cost' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Cost" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" data-column-toggle='notes' onChange="showTableColumns(this)" type="checkbox" id="chkCol_Notes" checked> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div> </div>
</li> </li>
@@ -81,7 +82,7 @@
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item">
<div class="list-group-item"> <div class="list-group-item">
<input class="form-check-input" onChange="showTableColumns(this, true)" type="checkbox" id="@elementId"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div> </div>
</li> </li>
@@ -106,14 +107,14 @@
<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" data-column="date">@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" data-column="odometer">@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" data-column="description">@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" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<th scope="col" style='display:none;' class="col-2" data-column="@extraFieldColumn">@extraFieldColumn</th> <th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
} }
</tr> </tr>
</thead> </thead>
@@ -121,14 +122,14 @@
@foreach (UpgradeRecord upgradeRecord in Model) @foreach (UpgradeRecord upgradeRecord in Model)
{ {
<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)'> <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" data-column="date">@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" data-column="odometer">@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" data-column="description">@upgradeRecord.Description</td> <td class="col-3 flex-grow-1 col-xl-4" data-column="description">@upgradeRecord.Description</td>
<td class="col-2" data-column="cost" 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" data-column="notes">@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) @foreach (string extraFieldColumn in extraFields)
{ {
<td class="col-2 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(upgradeRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td> <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>
} }
@@ -155,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>
<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> </ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View 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>

View File

@@ -26,50 +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">
<label for="inputPurchaseDate">@translator.Translate(userLanguage, "Purchased Date(optional)")</label> <label for="inputLicensePlate">@translator.Translate(userLanguage, "License Plate")</label>
<input type="text" id="inputPurchaseDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Date")" value="@Model.PurchaseDate"> <input type="text" id="inputLicensePlate" class="form-control" placeholder="@translator.Translate(userLanguage, "License Plate")" value="@Model.LicensePlate">
<label for="inputSoldDate">@translator.Translate(userLanguage, "Sold Date(optional)")</label> @foreach (ExtraField field in Model.ExtraFields)
<input type="text" id="inputSoldDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Date")" value="@Model.SoldDate"> {
<div class="form-check form-switch"> var elementId = Guid.NewGuid();
<input class="form-check-input" type="checkbox" role="switch" id="inputIsElectric" checked="@Model.IsElectric"> <div class="extra-field">
<label class="form-check-label" for="inputIsElectric">@translator.Translate(userLanguage, "Electric Vehicle")</label> <label for="@elementId">@field.Name</label>
</div> <input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
<div class="form-check form-switch"> </div>
<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>
@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">
@if (!string.IsNullOrWhiteSpace(Model.ImageLocation)) <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="inputImage">@translator.Translate(userLanguage, "Replace picture(optional)")</label> <label for="inputSoldDate">@translator.Translate(userLanguage, "Sold Date(optional)")</label>
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage"> <input type="text" id="inputSoldDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Date")" value="@Model.SoldDate">
} else <div class="form-check form-switch">
{ <input class="form-check-input" type="checkbox" role="switch" id="inputIsElectric" checked="@Model.IsElectric">
<label for="inputImage">@translator.Translate(userLanguage, "Upload a picture(optional)")</label> <label class="form-check-label" for="inputIsElectric">@translator.Translate(userLanguage, "Electric Vehicle")</label>
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage"> </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>
</form> </form>
</div> </div>

View File

@@ -19,7 +19,9 @@
"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 ],

View 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:

View File

@@ -82,6 +82,10 @@ html {
overflow: visible; overflow: visible;
} }
body {
background-color: #fff !important;
}
table { table {
background-color: #fff !important; background-color: #fff !important;
} }
@@ -367,3 +371,6 @@ input[type="file"] {
position: absolute; position: absolute;
z-index: 10; z-index: 10;
} }
.copyable{
cursor: pointer;
}

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@@ -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();
@@ -403,6 +405,16 @@ function searchGasTableRows() {
if (result.isConfirmed) { if (result.isConfirmed) {
var rowData = $(`#${tabName} table tbody tr`); var rowData = $(`#${tabName} table tbody tr`);
var filteredRows = $(`#${tabName} table tbody tr td:contains('${result.value.searchString}')`).parent(); 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() == '') { if (result.value.searchString.trim() == '') {
rowData.removeClass('override-hide'); rowData.removeClass('override-hide');
} else { } else {
@@ -415,3 +427,68 @@ function searchGasTableRows() {
} }
}); });
} }
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());
}
})
}

View File

@@ -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,11 +104,18 @@ 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,
@@ -113,3 +123,76 @@ function getAndValidateOdometerRecordValues() {
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());
}
})
}

View File

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

View File

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

View File

@@ -42,6 +42,9 @@ function saveVehicle(isEdit) {
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); var extraFields = getAndValidateExtraFields(true);
//validate //validate
var hasError = false; var hasError = false;
@@ -72,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;
} }
@@ -87,7 +107,10 @@ function saveVehicle(isEdit) {
useHours: vehicleUseHours, useHours: vehicleUseHours,
extraFields: extraFields.extraFields, extraFields: extraFields.extraFields,
purchaseDate: vehiclePurchaseDate, purchaseDate: vehiclePurchaseDate,
soldDate: vehicleSoldDate soldDate: vehicleSoldDate,
hasOdometerAdjustment: vehicleHasOdometerAdjustment,
odometerMultiplier: vehicleOdometerMultiplier,
odometerDifference: vehicleOdometerDifference
}, function (data) { }, function (data) {
if (data) { if (data) {
if (!isEdit) { if (!isEdit) {
@@ -105,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]);
@@ -292,6 +323,16 @@ function updateAggregateLabels() {
} }
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']");
@@ -643,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();
@@ -662,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;
@@ -678,6 +719,9 @@ function isRightClick(e) {
return false; return false;
} }
function stopEvent() { function stopEvent() {
if (isDragging) {
isDragging = false;
}
event.stopPropagation(); event.stopPropagation();
} }
function rangeMouseUp(e) { function rangeMouseUp(e) {
@@ -770,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](),
@@ -783,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'));
@@ -847,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 }
@@ -879,25 +928,16 @@ function replenishSupplies() {
} }
}); });
} }
function showTableColumns(e, isExtraField) { function showTableColumns(e, tabName) {
//logic for extra field since we dont hardcode the data-column type //logic for extra field since we dont hardcode the data-column type
if (isExtraField) { var showColumn = $(e).is(':checked');
var showColumn = $(e).is(':checked'); var columnName = $(e).attr('data-column-toggle');
var columnName = $(e).parent().find('.form-check-label').text(); if (showColumn) {
if (showColumn) { $(`[data-column='${columnName}']`).show();
$(`[data-column='${columnName}']`).show();
} else {
$(`[data-column='${columnName}']`).hide();
}
} else { } else {
var showColumn = $(e).is(':checked'); $(`[data-column='${columnName}']`).hide();
var columnName = $(e).attr('data-column-toggle');
if (showColumn) {
$(`[data-column='${columnName}']`).show();
} else {
$(`[data-column='${columnName}']`).hide();
}
} }
saveUserColumnPreferences(tabName);
} }
function searchTableRows(tabName) { function searchTableRows(tabName) {
Swal.fire({ Swal.fire({
@@ -915,6 +955,16 @@ function searchTableRows(tabName) {
if (result.isConfirmed) { if (result.isConfirmed) {
var rowData = $(`#${tabName} table tbody tr`); var rowData = $(`#${tabName} table tbody tr`);
var filteredRows = $(`#${tabName} table tbody tr td:contains('${result.value.searchString}')`).parent(); 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() == '') { if (result.value.searchString.trim() == '') {
rowData.removeClass('override-hide'); rowData.removeClass('override-hide');
} else { } else {
@@ -926,3 +976,38 @@ function searchTableRows(tabName) {
} }
}); });
} }
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");
}

View File

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

View File

@@ -441,3 +441,117 @@ function getAndValidateGenericRecordValues() {
} }
} }
} }
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();
}
});
}