Compare commits

..

85 Commits

Author SHA1 Message Date
Hargata Softworks
4d5020b607 Merge pull request #839 from hargata/Hargata/805
re-send registration token email.
2025-02-03 08:51:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7d8b7596ce fixed wording. 2025-02-03 08:51:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
08b5c9f25a re-send registration token email. 2025-02-03 08:35:33 -07:00
Hargata Softworks
82a4f8d57b Merge pull request #833 from hargata/Hargata/831
translatable Email correspondence
2025-01-26 10:28:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
538a79319e translatable Email correspondence 2025-01-26 10:26:41 -07:00
Hargata Softworks
0555dcbc43 Merge pull request #830 from hargata/Hargata/824
take into account purchase and sold date.
2025-01-24 14:02:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7b1f19ff9f take into account purchase and sold date. 2025-01-24 14:00:26 -07:00
Hargata Softworks
9cac427eb9 Merge pull request #829 from hargata/Hargata/823
add attachments column
2025-01-24 13:13:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1e5950028f add attachments column 2025-01-24 13:01:56 -07:00
Hargata Softworks
7a7d343c3f Merge pull request #828 from hargata/Hargata/824
additional logic taking into account vehicle sold date.
2025-01-24 10:40:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ebf6388414 additional logic taking into account vehicle sold date. 2025-01-24 10:39:42 -07:00
Hargata Softworks
fa5426be53 Merge pull request #827 from hargata/Hargata/824
Fix number of days used when calculating cost per day.
2025-01-24 10:22:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4c60bb20c9 Fix number of days used when calculating cost per day. 2025-01-24 10:21:40 -07:00
Hargata Softworks
b02f3c8f8b Merge pull request #826 from hargata/Hargata/825
825
2025-01-24 09:17:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d5f769e5a4 Fixed the cost per distance sum table to use total cost per year divided by total distance per year. 2025-01-24 09:16:42 -07:00
Hargata Softworks
2afd1188eb Merge pull request #820 from hargata/Hargata/804
only make certain api endpoints testable
2025-01-22 12:09:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c6301242d make urgencies endpoint parameters optional. 2025-01-22 12:07:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
06016727d9 only make certain api endpoints testable 2025-01-22 11:52:16 -07:00
Hargata Softworks
1ee0c27ff9 Merge pull request #819 from hargata/Hargata/804
make it easier to test API.
2025-01-22 11:34:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7f03a630c6 make it easier to test API. 2025-01-22 11:33:55 -07:00
Hargata Softworks
2aa19f4f3b Merge pull request #818 from hargata/Hargata/tag.bug.fix
Fix count labels for odometer and reminder records when filtered by tags
2025-01-22 09:40:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4be3c16adc Fix count labels for odometer and reminder records when filtered by tags. 2025-01-22 09:39:07 -07:00
Hargata Softworks
f1f99a67dd Merge pull request #816 from hargata/Hargata/refine.sort
only add the default sorter once.
2025-01-21 15:59:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a4f15ffe15 only add the default sorter once. 2025-01-21 15:57:50 -07:00
Hargata Softworks
c11dad0cb3 Merge pull request #815 from hargata/Hargata/refine.sort
get rid of storedRowTableState and all the bugs that comes with it.
2025-01-21 14:11:28 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2bf2469657 get rid of storedRowTableState and all the bugs that comes with it. 2025-01-21 14:08:44 -07:00
Hargata Softworks
a23b02e962 Merge pull request #814 from hargata/Hargata/805.2
streamline token generation process.
2025-01-20 13:14:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
905ee9bf27 streamline token generation process. 2025-01-20 13:14:04 -07:00
Hargata Softworks
e26869b30b Merge pull request #813 from hargata/Hargata/755
add days as an interval for recurring tax records and reminders
2025-01-20 12:55:56 -07:00
DESKTOP-T0O5CDB\DESK-555BD
90867792d9 missing semicolon 2025-01-20 12:51:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1238802a75 More Month to Time update. 2025-01-20 12:51:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8f7c50b88f Change label. 2025-01-20 12:48:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
df24434689 add days as an interval for recurring tax records and reminders 2025-01-20 12:42:59 -07:00
Hargata Softworks
878748803d Merge pull request #812 from hargata/Hargata/tech.debt
Fixed unreachable code and used Append instead of Add.
2025-01-19 12:34:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
80bbf7f22f Fixed unreachable code and used Append instead of Add. 2025-01-19 12:33:50 -07:00
Hargata Softworks
28b0ea565a Merge pull request #811 from hargata/Hargata/801.3
Add sticker printing for supplies.
2025-01-19 10:37:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
07ccd1dea9 Add sticker printing for supplies. 2025-01-19 10:36:32 -07:00
Hargata Softworks
6a8fba535a Merge pull request #810 from hargata/Hargata/801.2
added extra field and supplies to the sticker print.
2025-01-17 11:37:10 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f1b82ad0d3 removed unused row class. 2025-01-17 11:36:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0035bc09dd added extra field and supplies to the sticker print. 2025-01-17 11:35:41 -07:00
Hargata Softworks
863c57d7fb Merge pull request #808 from hargata/Hargata/801.2
add sticker printing for odometer and plan records.
2025-01-17 09:11:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
00ccb68ce2 add sticker printing for odometer and plan records. 2025-01-17 09:09:19 -07:00
Hargata Softworks
c2995dcd25 Merge pull request #806 from hargata/Hargata/805
enable open registration.
2025-01-16 11:23:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7b63366627 enable open registration. 2025-01-16 11:20:44 -07:00
Hargata Softworks
ababa6bf27 Merge pull request #803 from hargata/Hargata/801
Hargata/801 - Printable Stickers
2025-01-16 10:24:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3f1f42d4f0 add vehicle extra fields and fixed styling 2025-01-16 10:24:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d76bd6d36 added printing functionality for notes, fuel, and tax. 2025-01-15 11:54:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ab34d1682c Added ability to print individual service, repair, and upgrade records. 2025-01-15 09:49:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
552b7a6356 simplified design. 2025-01-12 15:26:08 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f86acf673a you can now print reminders. 2025-01-12 14:47:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c6f49bafca Added sticker viewModel and some code cleanup. 2025-01-12 12:28:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fdbb325611 simplify code. 2025-01-12 12:04:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8c557ced85 draft PR for sticker printing. 2025-01-11 12:04:01 -07:00
Hargata Softworks
ad0f7de506 Merge pull request #802 from hargata/Hargata/697
Document the Calendar API along with support for auth.
2025-01-10 19:46:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
15ec9bb454 Document the Calendar API along with support for auth. 2025-01-10 19:43:09 -07:00
Hargata Softworks
9fe7558cfe Merge pull request #792 from hargata/Hargata/260
Simplify volume mounts.
2025-01-10 13:53:31 -07:00
Hargata Softworks
79633dbe1d Merge pull request #799 from hargata/Hargata/796
Update configurator
2025-01-10 12:13:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f325306e20 Update configurator 2025-01-10 12:13:34 -07:00
Hargata Softworks
4778656063 Merge pull request #798 from hargata/Hargata/796
fix mileage not included.
2025-01-10 11:02:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f7e00523c2 fix mileage not included. 2025-01-10 11:01:24 -07:00
Hargata Softworks
bf3df7230b Merge pull request #797 from hargata/Hargata/796
Hargata/796
2025-01-10 10:03:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c5182e0ed6 lol 2025-01-10 10:03:09 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4081438cba Defer calculations if odometer not provided. 2025-01-10 10:01:16 -07:00
Hargata Softworks
b72fe2bf37 Merge pull request #793 from hargata/Hargata/warn.dir.diff
Warn when working directory differs from content path.
2025-01-09 10:02:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d9c687709 Warn when working directory differs from content path. 2025-01-09 10:02:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3d4b970967 Simplify volume mounts. 2025-01-08 20:56:37 -07:00
Hargata Softworks
792f295c45 Merge pull request #791 from hargata/Hargata/769
Fixed backup restoration for userConfig.json
2025-01-08 20:38:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
af28753558 use a static string instead. 2025-01-08 20:37:45 -07:00
DESKTOP-T0O5CDB\DESK-555BD
24fb663599 Fixed backup restoration for userConfig.json 2025-01-08 20:33:38 -07:00
Hargata Softworks
140506c9c3 Merge pull request #790 from hargata/Hargata/769
moved userConfig file into data folder.
2025-01-08 20:26:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
519f159c8c moved userConfig file into data folder. 2025-01-08 20:25:17 -07:00
Hargata Softworks
bb019cbcd9 Merge pull request #789 from hargata/Hargata/769
Fix gas record files attachment
2025-01-08 16:19:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
84219627ff Fix gas record files attachment 2025-01-08 16:18:36 -07:00
Hargata Softworks
f218e878c6 Merge pull request #788 from hargata/Hargata/769
Document Upload Endpoint
2025-01-08 15:55:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
520f47955b cleaned up usings. 2025-01-08 14:15:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8110ee18f1 Create endpoint to upload documents and add ability to attach uploaded documents to records. 2025-01-08 14:14:23 -07:00
Hargata Softworks
55bf817310 Merge pull request #782 from hargata/Hargata/697
added API method that generates a calendar of reminders for the user.
2025-01-08 12:39:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2f55f8118 Restore favicon, previously was served up automatically by browser but now we want to link it explicitly. also brought back the security features for accessing static files. 2025-01-08 12:38:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f48e7cd0d4 update Controller Action Name, 2025-01-08 11:11:29 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c82e0c8b9b Use a MD5 hash to get exactly 16 bytes so the GUID is always valid and identical for the calendar event with same date and description. 2025-01-08 10:49:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c72877e16b Merge branch 'main' into Hargata/697 2025-01-08 10:40:10 -07:00
Hargata Softworks
ccc9076397 Merge pull request #786 from hargata/Hargata/785
move files out of webrootpath.
2025-01-08 10:13:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c716ecf4a bump version 2025-01-08 10:12:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2cc471b944 move files out of webrootpath. 2025-01-08 10:10:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a6c450109a added API method that generates a calendar of reminders for the user. 2025-01-07 08:43:25 -07:00
68 changed files with 1367 additions and 264 deletions

11
.gitignore vendored
View File

@@ -1,15 +1,6 @@
.vs/
bin/
obj/
wwwroot/images/
cartracker.db
data/cartracker.db
wwwroot/documents/
wwwroot/temp/
wwwroot/imports/
wwwroot/translations/
config/userConfig.json
data/
CarCareTracker.csproj.user
Properties/launchSettings.json
data/cartracker-log.db
data/widgets.html

View File

@@ -34,6 +34,7 @@ namespace CarCareTracker.Controllers
private readonly IFileHelper _fileHelper;
private readonly IMailHelper _mailHelper;
private readonly IConfigHelper _config;
private readonly IWebHostEnvironment _webEnv;
public APIController(IVehicleDataAccess dataAccess,
IGasHelper gasHelper,
IReminderHelper reminderHelper,
@@ -55,7 +56,8 @@ namespace CarCareTracker.Controllers
IConfigHelper config,
IUserLogic userLogic,
IVehicleLogic vehicleLogic,
IOdometerLogic odometerLogic)
IOdometerLogic odometerLogic,
IWebHostEnvironment webEnv)
{
_dataAccess = dataAccess;
_noteDataAccess = noteDataAccess;
@@ -79,6 +81,7 @@ namespace CarCareTracker.Controllers
_vehicleLogic = vehicleLogic;
_fileHelper = fileHelper;
_config = config;
_webEnv = webEnv;
}
public IActionResult Index()
{
@@ -163,7 +166,7 @@ namespace CarCareTracker.Controllers
return Json(response);
}
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Tags = string.Join(' ', x.Tags) });
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
@@ -206,6 +209,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
@@ -291,6 +295,7 @@ namespace CarCareTracker.Controllers
existingRecord.Description = input.Description;
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.Files = input.Files;
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
@@ -322,7 +327,7 @@ namespace CarCareTracker.Controllers
return Json(response);
}
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Tags = string.Join(' ', x.Tags) });
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
@@ -366,6 +371,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
@@ -453,6 +459,7 @@ namespace CarCareTracker.Controllers
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "repairrecord.update.api", User.Identity.Name));
@@ -484,7 +491,7 @@ namespace CarCareTracker.Controllers
return Json(response);
}
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Tags = string.Join(' ', x.Tags) });
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
@@ -528,6 +535,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
@@ -614,6 +622,7 @@ namespace CarCareTracker.Controllers
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "upgraderecord.update.api", User.Identity.Name));
@@ -644,7 +653,7 @@ namespace CarCareTracker.Controllers
Response.StatusCode = 400;
return Json(response);
}
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Select(x => new TaxRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields, Tags = string.Join(' ', x.Tags) });
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Select(x => new TaxRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
@@ -721,6 +730,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
@@ -790,6 +800,7 @@ namespace CarCareTracker.Controllers
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_taxRecordDataAccess.SaveTaxRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(existingRecord, "taxrecord.update.api", User.Identity.Name));
@@ -840,7 +851,7 @@ namespace CarCareTracker.Controllers
{
vehicleRecords = _odometerLogic.AutoConvertOdometerRecord(vehicleRecords);
}
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields, Tags = string.Join(' ', x.Tags) });
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Id = x.Id.ToString(), Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields, Files = x.Files, Tags = string.Join(' ', x.Tags) });
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
@@ -881,6 +892,7 @@ namespace CarCareTracker.Controllers
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),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
@@ -948,6 +960,7 @@ namespace CarCareTracker.Controllers
existingRecord.InitialMileage = int.Parse(input.InitialOdometer);
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.update.api", User.Identity.Name));
@@ -991,6 +1004,7 @@ namespace CarCareTracker.Controllers
MissedFuelUp = x.MissedFuelUp.ToString(),
Notes = x.Notes,
ExtraFields = x.ExtraFields,
Files = x.Files,
Tags = string.Join(' ', x.Tags)
});
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
@@ -1041,6 +1055,7 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Files = input.Files,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
@@ -1085,7 +1100,7 @@ namespace CarCareTracker.Controllers
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.delete.api", User.Identity.Name));
}
return Json(OperationResponse.Conditional(result, "Odometer Record Deleted"));
return Json(OperationResponse.Conditional(result, "Gas Record Deleted"));
}
[HttpPut]
[Route("/api/vehicle/gasrecords/update")]
@@ -1126,6 +1141,7 @@ namespace CarCareTracker.Controllers
existingRecord.Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes;
existingRecord.Cost = decimal.Parse(input.Cost);
existingRecord.ExtraFields = input.ExtraFields;
existingRecord.Files = input.Files;
existingRecord.Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList();
_gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.update.api", User.Identity.Name));
@@ -1166,11 +1182,61 @@ namespace CarCareTracker.Controllers
return Json(results);
}
}
[HttpGet]
[Route("/api/calendar")]
public IActionResult Calendar()
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
var calendarContent = StaticHelper.RemindersToCalendar(reminders);
return File(calendarContent, "text/calendar");
}
[HttpPost]
[Route("/api/documents/upload")]
public IActionResult UploadDocument(List<IFormFile> documents)
{
if (documents.Any())
{
List<UploadedFiles> uploadedFiles = new List<UploadedFiles>();
string uploadDirectory = "documents/";
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
foreach (IFormFile document in documents)
{
string fileName = Guid.NewGuid() + Path.GetExtension(document.FileName);
string filePath = Path.Combine(uploadPath, fileName);
using (var stream = System.IO.File.Create(filePath))
{
document.CopyTo(stream);
}
uploadedFiles.Add(new UploadedFiles
{
Location = Path.Combine("/", uploadDirectory, fileName),
Name = Path.GetFileName(document.FileName)
});
}
return Json(uploadedFiles);
} else
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("No files to upload"));
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
[Route("/api/vehicle/reminders/send")]
public IActionResult SendReminders(List<ReminderUrgency> urgencies)
{
if (!urgencies.Any())
{
//if no urgencies parameter, we will default to all urgencies.
urgencies = new List<ReminderUrgency> { ReminderUrgency.NotUrgent, ReminderUrgency.Urgent, ReminderUrgency.VeryUrgent, ReminderUrgency.PastDue };
}
var vehicles = _dataAccess.GetVehicles();
List<OperationResponse> operationResponses = new List<OperationResponse>();
var defaultEmailAddress = _config.GetUserConfig(User).DefaultReminderEmail;

View File

@@ -81,7 +81,7 @@ namespace CarCareTracker.Controllers
private string UploadFile(IFormFile fileToUpload)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
string fileName = Guid.NewGuid() + Path.GetExtension(fileToUpload.FileName);
@@ -95,7 +95,7 @@ namespace CarCareTracker.Controllers
public IActionResult UploadCoordinates(List<string> coordinates)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
string fileName = Guid.NewGuid() + ".csv";

View File

@@ -98,7 +98,6 @@ namespace CarCareTracker.Controllers
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
return PartialView("_KioskPlan", kioskResult);
}
break;
case KioskMode.Reminder:
{
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);

View File

@@ -240,6 +240,12 @@ namespace CarCareTracker.Controllers
return Json(result);
}
[HttpPost]
public IActionResult SendRegistrationToken(LoginModel credentials)
{
var result = _loginLogic.SendRegistrationToken(credentials);
return Json(result);
}
[HttpPost]
public IActionResult RequestResetPassword(LoginModel credentials)
{
var result = _loginLogic.RequestResetPassword(credentials);

View File

@@ -25,7 +25,7 @@ namespace CarCareTracker.Controllers
return Json(false);
}
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";

View File

@@ -156,6 +156,7 @@ namespace CarCareTracker.Controllers
ReminderMonthInterval = result.ReminderMonthInterval,
CustomMileageInterval = result.CustomMileageInterval,
CustomMonthInterval = result.CustomMonthInterval,
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
Tags = result.Tags
};
return PartialView("_ReminderRecordModal", convertedResult);

View File

@@ -196,7 +196,7 @@ namespace CarCareTracker.Controllers
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var userConfig = _config.GetUserConfig(User);
var totalDistanceTraveled = maxMileage - minMileage;
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, year, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
var viewModel = new CostTableForVehicle
{
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),

View File

@@ -90,6 +90,7 @@ namespace CarCareTracker.Controllers
IsRecurring = result.IsRecurring,
RecurringInterval = result.RecurringInterval,
CustomMonthInterval = result.CustomMonthInterval,
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
Files = result.Files,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)

View File

@@ -1,12 +1,12 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
using CarCareTracker.Helper;
using System.Globalization;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using CarCareTracker.Logic;
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Logic;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
using System.Security.Claims;
using System.Text.Json;
namespace CarCareTracker.Controllers
@@ -131,7 +131,8 @@ namespace CarCareTracker.Controllers
{
_userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Created Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.add", User.Identity.Name, vehicleInput.Id.ToString()));
} else
}
else
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Updated Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.update", User.Identity.Name, vehicleInput.Id.ToString()));
}
@@ -198,7 +199,7 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed());
}
}
#region "Shared Methods"
[HttpPost]
public IActionResult GetFilesPendingUpload(List<UploadedFiles> uploadedFiles)
@@ -215,7 +216,7 @@ namespace CarCareTracker.Controllers
{
return Json(searchResults);
}
foreach(ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs)
foreach (ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs)
{
switch (visibleTab)
{
@@ -843,14 +844,15 @@ namespace CarCareTracker.Controllers
}
if (extraFieldIsEdited)
{
foreach(ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields)
foreach (ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields)
{
if (existingRecord.ExtraFields.Any(x=>x.Name == extraField.Name))
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
{
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
existingRecord.ExtraFields.Insert(insertIndex, extraField);
} else
}
else
{
existingRecord.ExtraFields.Add(extraField);
}
@@ -956,6 +958,167 @@ namespace CarCareTracker.Controllers
return Json(result);
}
[HttpPost]
public IActionResult PrintRecordStickers(int vehicleId, List<int> recordIds, ImportMode importMode)
{
bool result = false;
if (!recordIds.Any())
{
return Json(result);
}
var stickerViewModel = new StickerViewModel() { RecordType = importMode };
if (vehicleId != default)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
if (vehicleData != null && vehicleData.Id != default)
{
stickerViewModel.VehicleData = vehicleData;
}
}
int recordsAdded = 0;
switch (importMode)
{
case ImportMode.ServiceRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.GenericRecords.Add(_serviceRecordDataAccess.GetServiceRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.RepairRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.GenericRecords.Add(_collisionRecordDataAccess.GetCollisionRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.UpgradeRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.GenericRecords.Add(_upgradeRecordDataAccess.GetUpgradeRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.GasRecord:
{
foreach (int recordId in recordIds)
{
var record = _gasRecordDataAccess.GetGasRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Cost = record.Cost,
Date = record.Date,
Notes = record.Notes,
Mileage = record.Mileage,
ExtraFields = record.ExtraFields
});
recordsAdded++;
}
}
break;
case ImportMode.TaxRecord:
{
foreach (int recordId in recordIds)
{
var record = _taxRecordDataAccess.GetTaxRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Description = record.Description,
Cost = record.Cost,
Notes = record.Notes,
Date = record.Date,
ExtraFields = record.ExtraFields
});
recordsAdded++;
}
}
break;
case ImportMode.SupplyRecord:
{
foreach (int recordId in recordIds)
{
var record = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
stickerViewModel.SupplyRecords.Add(record);
recordsAdded++;
}
}
break;
case ImportMode.NoteRecord:
{
foreach (int recordId in recordIds)
{
var record = _noteDataAccess.GetNoteById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Description = record.Description,
Notes = record.NoteText
});
recordsAdded++;
}
}
break;
case ImportMode.OdometerRecord:
{
foreach (int recordId in recordIds)
{
var record = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Date = record.Date,
Mileage = record.Mileage,
Notes = record.Notes,
ExtraFields = record.ExtraFields
});
recordsAdded++;
}
}
break;
case ImportMode.ReminderRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.ReminderRecords.Add(_reminderRecordDataAccess.GetReminderRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.PlanRecord:
{
foreach (int recordId in recordIds)
{
var record = _planRecordDataAccess.GetPlanRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Description = record.Description,
Cost = record.Cost,
Notes = record.Notes,
Date = record.DateModified,
ExtraFields = record.ExtraFields,
RequisitionHistory = record.RequisitionHistory
});
recordsAdded++;
}
}
break;
}
if (recordsAdded > 0)
{
return PartialView("_Stickers", stickerViewModel);
}
return Json(result);
}
[HttpPost]
public IActionResult SaveUserColumnPreferences(UserColumnPreference columnPreference)
{
try

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public enum ReminderIntervalUnit
{
Months = 1,
Days = 2
}
}

View File

@@ -26,6 +26,7 @@ namespace CarCareTracker.Helper
string GetAllowedFileUploadExtensions();
bool DeleteUserConfig(int userId);
bool GetInvariantApi();
bool GetServerOpenRegistration();
}
public class ConfigHelper : IConfigHelper
{
@@ -61,6 +62,10 @@ namespace CarCareTracker.Helper
var motd = CheckString("LUBELOGGER_MOTD");
return motd;
}
public bool GetServerOpenRegistration()
{
return CheckBool(CheckString("LUBELOGGER_OPEN_REGISTRATION"));
}
public OpenIDConfig GetOpenIDConfig()
{
OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig();

View File

@@ -34,7 +34,7 @@ namespace CarCareTracker.Helper
}
public List<string> GetLanguages()
{
var languagePath = Path.Combine(_webEnv.WebRootPath, "translations");
var languagePath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
var defaultList = new List<string>() { "en_US" };
if (Directory.Exists(languagePath))
{
@@ -72,7 +72,7 @@ namespace CarCareTracker.Helper
{
currentFilePath = currentFilePath.Substring(1);
}
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
string oldFilePath = currentFilePath.StartsWith("defaults/") ? Path.Combine(_webEnv.WebRootPath, currentFilePath) : Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
if (File.Exists(oldFilePath))
{
return oldFilePath;
@@ -94,7 +94,7 @@ namespace CarCareTracker.Helper
}
try
{
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{Guid.NewGuid()}");
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{Guid.NewGuid()}");
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
//extract zip file
@@ -105,10 +105,10 @@ namespace CarCareTracker.Helper
var translationPath = Path.Combine(tempPath, "translations");
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
var configPath = Path.Combine(tempPath, StaticHelper.LegacyUserConfigPath);
if (Directory.Exists(imagePath))
{
var existingPath = Path.Combine(_webEnv.WebRootPath, "images");
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
if (!Directory.Exists(existingPath))
{
Directory.CreateDirectory(existingPath);
@@ -130,7 +130,7 @@ namespace CarCareTracker.Helper
}
if (Directory.Exists(documentPath))
{
var existingPath = Path.Combine(_webEnv.WebRootPath, "documents");
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
if (!Directory.Exists(existingPath))
{
Directory.CreateDirectory(existingPath);
@@ -152,7 +152,7 @@ namespace CarCareTracker.Helper
}
if (Directory.Exists(translationPath))
{
var existingPath = Path.Combine(_webEnv.WebRootPath, "translations");
var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
if (!Directory.Exists(existingPath))
{
Directory.CreateDirectory(existingPath);
@@ -186,9 +186,9 @@ namespace CarCareTracker.Helper
if (File.Exists(configPath))
{
//check if config folder exists.
if (!Directory.Exists("config/"))
if (!Directory.Exists("data/config"))
{
Directory.CreateDirectory("config/");
Directory.CreateDirectory("data/config");
}
File.Move(configPath, StaticHelper.UserConfigPath, true);
}
@@ -203,7 +203,7 @@ namespace CarCareTracker.Helper
public string MakeAttachmentsExport(List<GenericReportModel> exportData)
{
var folderName = Guid.NewGuid();
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
int fileIndex = 0;
@@ -227,10 +227,10 @@ namespace CarCareTracker.Helper
public string MakeBackup()
{
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
var imagePath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
var documentPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
var translationPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
var dataPath = StaticHelper.DbName;
var widgetPath = StaticHelper.AdditionalWidgetsPath;
var configPath = StaticHelper.UserConfigPath;
@@ -301,8 +301,8 @@ namespace CarCareTracker.Helper
{
currentFilePath = currentFilePath.Substring(1);
}
string uploadPath = Path.Combine(_webEnv.WebRootPath, newFolder);
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", newFolder);
string oldFilePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
string newFileUploadPath = oldFilePath.Replace(tempPath, newFolder);
@@ -319,7 +319,7 @@ namespace CarCareTracker.Helper
{
currentFilePath = currentFilePath.Substring(1);
}
string filePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
string filePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
if (File.Exists(filePath))
{
File.Delete(filePath);

View File

@@ -76,7 +76,8 @@ namespace CarCareTracker.Helper
MissedFuelUp = currentObject.MissedFuelUp,
Notes = currentObject.Notes,
Tags = currentObject.Tags,
ExtraFields = currentObject.ExtraFields
ExtraFields = currentObject.ExtraFields,
Files = currentObject.Files
};
if (currentObject.MissedFuelUp)
{
@@ -86,9 +87,9 @@ namespace CarCareTracker.Helper
unFactoredConsumption = 0;
unFactoredMileage = 0;
}
else if (currentObject.IsFillToFull)
else if (currentObject.IsFillToFull && currentObject.Mileage != default)
{
//if user filled to full.
//if user filled to full and an odometer is provided, otherwise we will defer calculations
if (convertedConsumption > 0.00M && deltaMileage > 0)
{
try
@@ -130,10 +131,14 @@ namespace CarCareTracker.Helper
MissedFuelUp = currentObject.MissedFuelUp,
Notes = currentObject.Notes,
Tags = currentObject.Tags,
ExtraFields = currentObject.ExtraFields
ExtraFields = currentObject.ExtraFields,
Files = currentObject.Files
});
}
previousMileage = currentObject.Mileage;
if (currentObject.Mileage != default)
{
previousMileage = currentObject.Mileage;
}
}
return computedResults;
}

View File

@@ -15,16 +15,21 @@ namespace CarCareTracker.Helper
public class MailHelper : IMailHelper
{
private readonly MailConfig mailConfig;
private readonly string serverLanguage;
private readonly IFileHelper _fileHelper;
private readonly ITranslationHelper _translator;
private readonly ILogger<MailHelper> _logger;
public MailHelper(
IConfigHelper config,
IFileHelper fileHelper,
ITranslationHelper translationHelper,
ILogger<MailHelper> logger
) {
//load mailConfig from Configuration
mailConfig = config.GetMailConfig();
serverLanguage = config.GetServerLanguage();
_fileHelper = fileHelper;
_translator = translationHelper;
_logger = logger;
}
public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
@@ -36,8 +41,8 @@ namespace CarCareTracker.Helper
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "Your Registration Token for LubeLogger";
string emailBody = $"A token has been generated on your behalf, please complete your registration for LubeLogger using the token: {token}";
string emailSubject = _translator.Translate(serverLanguage, "Your Registration Token for LubeLogger");
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please complete your registration for LubeLogger using the token")}: {token}";
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
@@ -57,8 +62,8 @@ namespace CarCareTracker.Helper
{
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "Your Password Reset Token for LubeLogger";
string emailBody = $"A token has been generated on your behalf, please reset your password for LubeLogger using the token: {token}";
string emailSubject = _translator.Translate(serverLanguage, "Your Password Reset Token for LubeLogger");
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please reset your password for LubeLogger using the token")}: {token}";
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
@@ -79,8 +84,8 @@ namespace CarCareTracker.Helper
{
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "Your User Account Update Token for LubeLogger";
string emailBody = $"A token has been generated on your behalf, please update your account for LubeLogger using the token: {token}";
string emailSubject = _translator.Translate(serverLanguage, "Your User Account Update Token for LubeLogger");
string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please update your account for LubeLogger using the token")}: {token}";
var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody);
if (result)
{
@@ -107,17 +112,18 @@ namespace CarCareTracker.Helper
}
//get email template, this file has to exist since it's a static file.
var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate);
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
string emailSubject = $"{_translator.Translate(serverLanguage, "Vehicle Reminders From LubeLogger")} - {DateTime.Now.ToShortDateString()}";
//construct html table.
string emailBody = File.ReadAllText(emailTemplatePath);
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}");
string tableHeader = $"<th>{_translator.Translate(serverLanguage, "Urgency")}</th><th>{_translator.Translate(serverLanguage, "Description")}</th><th>{_translator.Translate(serverLanguage, "Due")}</th>";
string tableBody = "";
foreach(ReminderRecordViewModel reminder in reminders)
{
var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date.ToShortDateString()} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}";
tableBody += $"<tr class='{reminder.Urgency}'><td>{StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency)}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
tableBody += $"<tr class='{reminder.Urgency}'><td>{_translator.Translate(serverLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
}
emailBody = emailBody.Replace("{TableBody}", tableBody);
emailBody = emailBody.Replace("{TableHeader}", tableHeader).Replace("{TableBody}", tableBody);
try
{
var result = SendEmail(emailAddresses, emailSubject, emailBody);

View File

@@ -25,7 +25,14 @@ namespace CarCareTracker.Helper
existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval);
} else
{
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
{
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
}
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
{
existingReminder.Date = newDate.Date.AddDays(existingReminder.CustomMonthInterval);
}
}
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
@@ -55,7 +62,14 @@ namespace CarCareTracker.Helper
}
else
{
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
{
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
}
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
{
existingReminder.Date = newDate.AddDays(existingReminder.CustomMonthInterval);
}
}
}
return existingReminder;

View File

@@ -1,6 +1,8 @@
using CarCareTracker.Models;
using CsvHelper;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace CarCareTracker.Helper
@@ -10,9 +12,10 @@ namespace CarCareTracker.Helper
/// </summary>
public static class StaticHelper
{
public const string VersionNumber = "1.4.2";
public const string VersionNumber = "1.4.4";
public const string DbName = "data/cartracker.db";
public const string UserConfigPath = "config/userConfig.json";
public const string UserConfigPath = "data/config/userConfig.json";
public const string LegacyUserConfigPath = "config/userConfig.json";
public const string AdditionalWidgetsPath = "data/widgets.html";
public const string GenericErrorMessage = "An error occurred, please try again later";
public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
@@ -240,7 +243,8 @@ namespace CarCareTracker.Helper
public static List<ExtraField> AddExtraFields(List<ExtraField> recordExtraFields, List<ExtraField> templateExtraFields)
{
if (!templateExtraFields.Any()) {
if (!templateExtraFields.Any())
{
return new List<ExtraField>();
}
if (!recordExtraFields.Any())
@@ -261,7 +265,7 @@ namespace CarCareTracker.Helper
extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired;
}
//append extra fields
foreach(ExtraField extraField in templateExtraFields)
foreach (ExtraField extraField in templateExtraFields)
{
if (!recordFieldNames.Contains(extraField.Name))
{
@@ -309,7 +313,8 @@ namespace CarCareTracker.Helper
if (mailConfig != null && !string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
Console.WriteLine($"SMTP Configured for {mailConfig.EmailServer}");
} else
}
else
{
Console.WriteLine("SMTP Not Configured");
}
@@ -317,7 +322,94 @@ namespace CarCareTracker.Helper
Console.WriteLine($"Message Of The Day: {motd}");
if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name))
{
Console.WriteLine("No Locale or Culture Configured for LubeLogger, Check Environment Variables");
Console.WriteLine("WARNING: No Locale or Culture Configured for LubeLogger, Check Environment Variables");
}
//Create folders if they don't exist.
if (!Directory.Exists("data"))
{
Directory.CreateDirectory("data");
Console.WriteLine("Created data directory");
}
if (!Directory.Exists("data/images"))
{
Console.WriteLine("Created images directory");
Directory.CreateDirectory("data/images");
}
if (!Directory.Exists("data/documents"))
{
Directory.CreateDirectory("data/documents");
Console.WriteLine("Created documents directory");
}
if (!Directory.Exists("data/translations"))
{
Directory.CreateDirectory("data/translations");
Console.WriteLine("Created translations directory");
}
if (!Directory.Exists("data/temp"))
{
Directory.CreateDirectory("data/temp");
Console.WriteLine("Created translations directory");
}
if (!Directory.Exists("data/config"))
{
Directory.CreateDirectory("data/config");
Console.WriteLine("Created config directory");
}
}
public static void CheckMigration(string webRootPath, string webContentPath)
{
//check if current working directory differs from content root.
if (Directory.GetCurrentDirectory() != webContentPath)
{
Console.WriteLine("WARNING: The Working Directory differs from the Web Content Path");
Console.WriteLine($"Working Directory: {Directory.GetCurrentDirectory()}");
Console.WriteLine($"Web Content Path: {webContentPath}");
}
//migrates all user-uploaded files from webroot to new data folder
//images
var imagePath = Path.Combine(webRootPath, "images");
var docsPath = Path.Combine(webRootPath, "documents");
var translationPath = Path.Combine(webRootPath, "translations");
var tempPath = Path.Combine(webRootPath, "temp");
if (File.Exists(LegacyUserConfigPath))
{
File.Move(LegacyUserConfigPath, UserConfigPath, true);
}
if (Directory.Exists(imagePath))
{
foreach (string fileToMove in Directory.GetFiles(imagePath))
{
var newFilePath = $"data/images/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Image: {Path.GetFileName(fileToMove)}");
}
}
if (Directory.Exists(docsPath))
{
foreach (string fileToMove in Directory.GetFiles(docsPath))
{
var newFilePath = $"data/documents/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Document: {Path.GetFileName(fileToMove)}");
}
}
if (Directory.Exists(translationPath))
{
foreach (string fileToMove in Directory.GetFiles(translationPath))
{
var newFilePath = $"data/translations/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Translation: {Path.GetFileName(fileToMove)}");
}
}
if (Directory.Exists(tempPath))
{
foreach (string fileToMove in Directory.GetFiles(tempPath))
{
var newFilePath = $"data/temp/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Temp File: {Path.GetFileName(fileToMove)}");
}
}
}
public static async void NotifyAsync(string webhookURL, WebHookPayload webHookPayload)
@@ -332,7 +424,8 @@ namespace CarCareTracker.Helper
webhookURL = webhookURL.Replace("discord://", "https://"); //cleanurl
//format to discord
httpClient.PostAsJsonAsync(webhookURL, DiscordWebHook.FromWebHookPayload(webHookPayload));
} else
}
else
{
httpClient.PostAsJsonAsync(webhookURL, webHookPayload);
}
@@ -370,12 +463,14 @@ namespace CarCareTracker.Helper
if (vehicle.VehicleIdentifier == "LicensePlate")
{
return vehicle.LicensePlate;
} else
}
else
{
if (vehicle.ExtraFields.Any(x=>x.Name == vehicle.VehicleIdentifier))
if (vehicle.ExtraFields.Any(x => x.Name == vehicle.VehicleIdentifier))
{
return vehicle.ExtraFields?.FirstOrDefault(x=>x.Name == vehicle.VehicleIdentifier)?.Value;
} else
return vehicle.ExtraFields?.FirstOrDefault(x => x.Name == vehicle.VehicleIdentifier)?.Value;
}
else
{
return "N/A";
}
@@ -402,10 +497,11 @@ namespace CarCareTracker.Helper
//Translations
public static string GetTranslationDownloadPath(string continent, string name)
{
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name)){
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name))
{
return string.Empty;
}
else
}
else
{
switch (continent)
{
@@ -424,8 +520,9 @@ namespace CarCareTracker.Helper
if (string.IsNullOrWhiteSpace(name))
{
return string.Empty;
} else
{
}
else
{
try
{
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
@@ -438,7 +535,8 @@ namespace CarCareTracker.Helper
{
return displayName;
}
} catch (Exception ex)
}
catch (Exception ex)
{
return name;
}
@@ -609,7 +707,8 @@ namespace CarCareTracker.Helper
if (input == 0M.ToString("C2") && hideZero)
{
return "---";
} else
}
else
{
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
}
@@ -669,5 +768,52 @@ namespace CarCareTracker.Helper
_csv.NextRecord();
}
}
public static byte[] RemindersToCalendar(List<ReminderRecordViewModel> reminders)
{
//converts reminders to iCal file
StringBuilder sb = new StringBuilder();
//start the calendar item
sb.AppendLine("BEGIN:VCALENDAR");
sb.AppendLine("VERSION:2.0");
sb.AppendLine("PRODID:lubelogger.com");
sb.AppendLine("CALSCALE:GREGORIAN");
sb.AppendLine("METHOD:PUBLISH");
//create events.
foreach(ReminderRecordViewModel reminder in reminders)
{
var dtStart = reminder.Date.Date.ToString("yyyyMMddTHHmm00");
var dtEnd = reminder.Date.Date.AddDays(1).AddMilliseconds(-1).ToString("yyyyMMddTHHmm00");
var calendarUID = new Guid(MD5.HashData(Encoding.UTF8.GetBytes($"{dtStart}_{reminder.Description}")));
sb.AppendLine("BEGIN:VEVENT");
sb.AppendLine("DTSTAMP:" + DateTime.Now.ToString("yyyyMMddTHHmm00"));
sb.AppendLine("UID:" + calendarUID);
sb.AppendLine("DTSTART:" + dtStart);
sb.AppendLine("DTEND:" + dtEnd);
sb.AppendLine($"SUMMARY:{reminder.Description}");
sb.AppendLine($"DESCRIPTION:{reminder.Description}");
switch (reminder.Urgency)
{
case ReminderUrgency.NotUrgent:
sb.AppendLine("PRIORITY:3");
break;
case ReminderUrgency.Urgent:
sb.AppendLine("PRIORITY:2");
break;
case ReminderUrgency.VeryUrgent:
sb.AppendLine("PRIORITY:1");
break;
case ReminderUrgency.PastDue:
sb.AppendLine("PRIORITY:1");
break;
}
sb.AppendLine("END:VEVENT");
}
//end calendar item
sb.AppendLine("END:VCALENDAR");
string calendarContent = sb.ToString();
return Encoding.UTF8.GetBytes(calendarContent);
}
}
}

View File

@@ -21,6 +21,7 @@ namespace CarCareTracker.Logic
OperationResponse RequestResetPassword(LoginModel credentials);
OperationResponse ResetPasswordByUser(LoginModel credentials);
OperationResponse ResetUserPassword(LoginModel credentials);
OperationResponse SendRegistrationToken(LoginModel credentials);
UserData ValidateUserCredentials(LoginModel credentials);
UserData ValidateOpenIDUser(LoginModel credentials);
bool CheckIfUserIsValid(int userId);
@@ -190,6 +191,16 @@ namespace CarCareTracker.Logic
return OperationResponse.Failed();
}
}
public OperationResponse SendRegistrationToken(LoginModel credentials)
{
if (_configHelper.GetServerOpenRegistration())
{
return GenerateUserToken(credentials.EmailAddress, true);
} else
{
return OperationResponse.Failed("Open Registration Disabled");
}
}
/// <summary>
/// Generates a token and notifies user via email so they can reset their password.
/// </summary>
@@ -310,7 +321,18 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
if (existingToken.Id != default)
{
return OperationResponse.Failed("There is an existing token tied to this email address");
if (autoNotify) //re-send email
{
var notificationResult = _mailHelper.NotifyUserForRegistration(emailAddress, existingToken.Body);
if (notificationResult.Success)
{
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}, a new email has been sent out");
} else
{
return notificationResult;
}
}
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}");
}
var token = new Token()
{

View File

@@ -13,7 +13,7 @@ namespace CarCareTracker.Logic
int GetMaxMileage(VehicleRecords vehicleRecords);
int GetMinMileage(int vehicleId);
int GetMinMileage(VehicleRecords vehicleRecords);
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
@@ -199,22 +199,44 @@ namespace CarCareTracker.Logic
}
return numbersArray.Any() ? numbersArray.Min() : 0;
}
public int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
public int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
{
var startDate = DateTime.Now;
var endDate = DateTime.Now;
if (!string.IsNullOrWhiteSpace(soldDate))
bool usePurchaseDate = false;
bool useSoldDate = false;
if (!string.IsNullOrWhiteSpace(soldDate) && DateTime.TryParse(soldDate, out DateTime vehicleSoldDate))
{
endDate = DateTime.Parse(soldDate);
if (year == default || year >= vehicleSoldDate.Year) //All Time is selected or the selected year is greater or equal to the year the vehicle is sold
{
endDate = vehicleSoldDate; //cap end date to vehicle sold date.
useSoldDate = true;
}
}
if (!string.IsNullOrWhiteSpace(purchaseDate))
if (!string.IsNullOrWhiteSpace(purchaseDate) && DateTime.TryParse(purchaseDate, out DateTime vehiclePurchaseDate))
{
//if purchase date is provided, then we just have to subtract the begin date to end date and return number of months
startDate = DateTime.Parse(purchaseDate);
if (year == default || year <= vehiclePurchaseDate.Year) //All Time is selected or the selected year is less or equal to the year the vehicle is purchased
{
startDate = vehiclePurchaseDate; //cap start date to vehicle purchase date
usePurchaseDate = true;
}
}
if (year != default)
{
var calendarYearStart = new DateTime(year, 1, 1);
var calendarYearEnd = new DateTime(year + 1, 1, 1);
if (!useSoldDate)
{
endDate = endDate > calendarYearEnd ? calendarYearEnd : endDate;
}
if (!usePurchaseDate)
{
startDate = startDate > calendarYearStart ? calendarYearStart : startDate;
}
var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays);
return timeElapsed;
}
var dateArray = new List<DateTime>();
var dateArray = new List<DateTime>() { startDate };
dateArray.AddRange(serviceRecords.Select(x => x.Date));
dateArray.AddRange(repairRecords.Select(x => x.Date));
dateArray.AddRange(gasRecords.Select(x => x.Date));
@@ -340,7 +362,8 @@ namespace CarCareTracker.Logic
bool RecurringTaxIsOutdated(TaxRecord taxRecord)
{
var monthInterval = taxRecord.RecurringInterval != ReminderMonthInterval.Other ? (int)taxRecord.RecurringInterval : taxRecord.CustomMonthInterval;
return DateTime.Now > taxRecord.Date.AddMonths(monthInterval);
bool addDays = taxRecord.RecurringInterval == ReminderMonthInterval.Other && taxRecord.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
return addDays ? DateTime.Now > taxRecord.Date.AddDays(monthInterval) : DateTime.Now > taxRecord.Date.AddMonths(monthInterval);
}
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var outdatedRecurringFees = result.Where(x => x.IsRecurring && RecurringTaxIsOutdated(x));
@@ -351,6 +374,7 @@ namespace CarCareTracker.Logic
{
var monthInterval = recurringFee.RecurringInterval != ReminderMonthInterval.Other ? (int)recurringFee.RecurringInterval : recurringFee.CustomMonthInterval;
bool isOutdated = true;
bool addDays = recurringFee.RecurringInterval == ReminderMonthInterval.Other && recurringFee.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
//update the original outdated tax record
recurringFee.IsRecurring = false;
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
@@ -361,9 +385,9 @@ namespace CarCareTracker.Logic
{
try
{
var nextDate = originalDate.AddMonths(monthInterval * monthMultiplier);
var nextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
monthMultiplier++;
var nextnextDate = originalDate.AddMonths(monthInterval * monthMultiplier);
var nextnextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
recurringFee.Date = nextDate;
recurringFee.Id = default; //new record
recurringFee.IsRecurring = DateTime.Now <= nextnextDate;

View File

@@ -154,6 +154,7 @@ namespace CarCareTracker.Middleware
if (value.ToString().ToLower() == "api")
{
Response.StatusCode = 401;
Response.Headers.Append("WWW-Authenticate", "Basic");
return Task.CompletedTask;
}
}

View File

@@ -23,6 +23,7 @@
public string Notes { get; set; }
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp); } }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp) || (Mileage == default && !MissedFuelUp); } }
}
}

View File

@@ -13,6 +13,7 @@
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
public int CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;

View File

@@ -13,6 +13,7 @@
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
public int CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
@@ -34,6 +35,7 @@
ReminderMonthInterval = ReminderMonthInterval,
CustomMileageInterval = CustomMileageInterval,
CustomMonthInterval = CustomMonthInterval,
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
Notes = Notes,
Tags = Tags
};

View File

@@ -43,7 +43,7 @@ namespace CarCareTracker.Models
public string Cost { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
}
public class GenericRecordExportModel
{
@@ -58,7 +58,8 @@ namespace CarCareTracker.Models
[JsonConverter(typeof(FromDecimalOptional))]
public string Cost { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
public class OdometerRecordExportModel
{
@@ -72,7 +73,8 @@ namespace CarCareTracker.Models
public string Odometer { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
public class TaxRecordExportModel
{
@@ -85,7 +87,8 @@ namespace CarCareTracker.Models
[JsonConverter(typeof(FromDecimalOptional))]
public string Cost { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
public class GasRecordExportModel
{
@@ -107,7 +110,8 @@ namespace CarCareTracker.Models
public string MissedFuelUp { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
public class ReminderExportModel
{
@@ -130,6 +134,6 @@ namespace CarCareTracker.Models
public string Priority { get; set; }
public string Progress { get; set; }
public string Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
}
}

View File

@@ -0,0 +1,11 @@
namespace CarCareTracker.Models
{
public class StickerViewModel
{
public ImportMode RecordType { get; set; }
public Vehicle VehicleData { get; set; } = new Vehicle();
public List<ReminderRecord> ReminderRecords { get; set; } = new List<ReminderRecord>();
public List<GenericRecord> GenericRecords { get; set; } = new List<GenericRecord>();
public List<SupplyRecord> SupplyRecords { get; set; } = new List<SupplyRecord>();
}
}

View File

@@ -11,6 +11,7 @@
public bool IsRecurring { get; set; } = false;
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();

View File

@@ -12,6 +12,7 @@
public bool IsRecurring { get; set; } = false;
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
@@ -25,6 +26,7 @@
IsRecurring = IsRecurring,
RecurringInterval = RecurringInterval,
CustomMonthInterval = CustomMonthInterval,
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
Files = Files,
Tags = Tags,
ExtraFields = ExtraFields

View File

@@ -7,11 +7,14 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
//Print Messages
StaticHelper.InitMessage(builder.Configuration);
//Check Migration
StaticHelper.CheckMigration(builder.Environment.WebRootPath, builder.Environment.ContentRootPath);
// Add services to the container.
builder.Services.AddControllersWithViews();
@@ -66,8 +69,8 @@ builder.Services.AddSingleton<IGasHelper, GasHelper>();
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
//configure logic
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
@@ -75,15 +78,6 @@ builder.Services.AddSingleton<IUserLogic, UserLogic>();
builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>();
builder.Services.AddSingleton<IVehicleLogic, VehicleLogic>();
if (!Directory.Exists("data"))
{
Directory.CreateDirectory("data");
}
if (!Directory.Exists("config"))
{
Directory.CreateDirectory("config");
}
//Additional JsonFile
builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true);
@@ -112,13 +106,57 @@ var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "images")),
RequestPath = "/images",
OnPrepareResponse = ctx =>
{
if (ctx.Context.Request.Path.StartsWithSegments("/images") || ctx.Context.Request.Path.StartsWithSegments("/documents"))
if (ctx.Context.Request.Path.StartsWithSegments("/images"))
{
ctx.Context.Response.Headers.Add("Cache-Control", "no-store");
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated)
{
ctx.Context.Response.Redirect("/Login");
}
}
}
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "documents")),
RequestPath = "/documents",
OnPrepareResponse = ctx =>
{
if (ctx.Context.Request.Path.StartsWithSegments("/documents"))
{
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated)
{
ctx.Context.Response.Redirect("/Login");
}
}
}
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "translations")),
RequestPath = "/translations"
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "temp")),
RequestPath = "/temp",
OnPrepareResponse = ctx =>
{
if (ctx.Context.Request.Path.StartsWithSegments("/temp"))
{
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated)
{
ctx.Context.Response.Redirect("/Login");

View File

@@ -30,7 +30,7 @@
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<div class="col-5 copyable testable">
<code>/api/vehicles</code>
</div>
<div class="col-3">
@@ -44,7 +44,7 @@
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<div class="col-5 copyable testable">
<code>/api/vehicle/info</code>
</div>
<div class="col-3">
@@ -118,6 +118,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -140,6 +141,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -192,6 +194,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -215,6 +218,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -267,6 +271,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -290,6 +295,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -342,6 +348,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -365,6 +372,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -430,6 +438,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -452,6 +461,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -510,6 +520,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -535,6 +546,7 @@
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
@@ -566,13 +578,43 @@
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/calendar</code>
</div>
<div class="col-3">
Returns reminder calendar in ICS format
</div>
<div class="col-3">
No Params
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/documents/upload</code>
</div>
<div class="col-3">
Upload Documents
</div>
<div class="col-3">
Body(form-data): {<br />
documents[] - Files to Upload<br />
}
</div>
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<div class="col-5 copyable testable">
<code>/api/vehicle/reminders/send</code>
</div>
<div class="col-3">
@@ -580,14 +622,14 @@
</div>
<div class="col-3">
(must be root user)<br />
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue](optional)
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<div class="col-5 copyable testable">
<code>/api/makebackup</code>
</div>
<div class="col-3">
@@ -601,7 +643,7 @@
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<div class="col-5 copyable testable">
<code>/api/cleanup</code>
</div>
<div class="col-3">
@@ -609,13 +651,20 @@
</div>
<div class="col-3">
(must be root user)<br />
deepClean(bool) - Perform deep clean
deepClean(bool) - Perform deep clean(optional)
</div>
</div>
}
<script>
$('.copyable').on('click', function (e) {
copyToClipboard(e.currentTarget);
if (e.ctrlKey || e.metaKey){
let targetElement = $(e.currentTarget);
if (targetElement.hasClass("testable")){
window.location = targetElement.text().trim();
}
} else {
copyToClipboard(e.currentTarget);
}
})
function showExtraFieldsInfo(){
Swal.fire({
@@ -624,4 +673,11 @@
icon: "info"
});
}
function showAttachmentsInfo(){
Swal.fire({
title: "Attaching Files",
html: "The Document Upload Endpoint will upload the files and provide a formatted output which you can pass into this method",
icon: "info"
});
}
</script>

View File

@@ -146,6 +146,8 @@
</div>
</div>
</div>
<div class="stickerPrintContainer hideOnPrint">
</div>
<script>
loadGarage();
bindWindowResize();

View File

@@ -3,6 +3,7 @@
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
var openRegistrationEnabled = config.GetServerOpenRegistration();
}
@model string
@{
@@ -17,7 +18,19 @@
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
<input type="text" id="inputToken" class="form-control">
@if (openRegistrationEnabled)
{
<div class="input-group">
<input type="text" id="inputToken" class="form-control">
<div class="input-group-text">
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendOpenIdRegistrationToken()"><i class="bi bi-send"></i></button>
</div>
</div>
}
else
{
<input type="text" id="inputToken" class="form-control">
}
</div>
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
@@ -46,4 +59,14 @@
}
});
}
function sendOpenIdRegistrationToken(){
var userEmail = decodeHTMLEntities('@Model');
$.post('/Login/SendRegistrationToken', { emailAddress: userEmail }, function (data) {
if (data.success) {
successToast(data.message);
} else {
errorToast(data.message);
}
});
}
</script>

View File

@@ -3,6 +3,7 @@
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
var openRegistrationEnabled = config.GetServerOpenRegistration();
}
@{
ViewData["Title"] = "Register";
@@ -16,10 +17,19 @@
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
<input type="text" id="inputToken" class="form-control">
@if (openRegistrationEnabled) {
<div class="input-group">
<input type="text" id="inputToken" class="form-control">
<div class="input-group-text">
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendRegistrationToken()"><i class="bi bi-send"></i></button>
</div>
</div>
} else {
<input type="text" id="inputToken" class="form-control">
}
</div>
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
<input type="text" id="inputEmail" class="form-control">
</div>
<div class="form-group">

View File

@@ -34,6 +34,7 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>@ViewData["Title"] - LubeLogger</title>
<link rel="icon" type="image/x-icon" href="~/favicon.ico">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
<link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" />

View File

@@ -158,6 +158,8 @@
<div class="modal-content" id="inputSuppliesModalContent"></div>
</div>
</div>
<div class="stickerPrintContainer hideOnPrint">
</div>
<script>
function GetVehicleId() {
return {

View File

@@ -0,0 +1,9 @@
@model List<UploadedFiles>
@if (Model.Any()){
<span class="position-relative">
<i class="bi bi-paperclip"></i>
<span class="position-absolute attachment-badge-xs start-100 translate-middle badge rounded-pill bg-secondary">
@Model.Count()
</span>
</span>
}

View File

@@ -78,6 +78,12 @@
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Notes" checked>
@@ -118,6 +124,7 @@
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
@@ -133,6 +140,7 @@
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(collisionRecord.Mileage == default ? "---" : collisionRecord.Mileage.ToString())</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@collisionRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(collisionRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", collisionRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -185,6 +193,8 @@
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>

View File

@@ -49,10 +49,10 @@
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}")}")</td>
}
}
}
}
</tr>
}
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex fw-bold">
@@ -65,9 +65,11 @@
var yearDataToDisplay = Model.CostData.Where(x => x.Year == year);
if (yearDataToDisplay != null && yearDataToDisplay != default)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(yearDataToDisplay.Sum(x => x.Cost).ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(yearDataToDisplay.Sum(x => x.DistanceTraveled) != default ? $"{yearDataToDisplay.Sum(x => x.DistanceTraveled).ToString("N0")} {Model.DistanceUnit}" : "---")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(yearDataToDisplay.Sum(x => x.CostPerDistanceTraveled).ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
var distanceTraveled = yearDataToDisplay.Sum(x => x.DistanceTraveled);
var costAccrued = yearDataToDisplay.Sum(x => x.Cost);
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(costAccrued.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(distanceTraveled != default ? $"{distanceTraveled.ToString("N0")} {Model.DistanceUnit}" : "---")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(distanceTraveled != default && costAccrued != default ? (costAccrued / distanceTraveled).ToString("C2") : 0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
}
else
{

View File

@@ -21,8 +21,8 @@
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Type")</th>
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")</th>
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)</th>
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")<span class="cost-table-hint d-none">@($"({Model.NumberOfDays.ToString("N0")})")</span></th>
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)<span class="cost-table-hint d-none">@($"({Model.TotalDistance.ToString("N0")})")</span></th>
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</th>
</tr>
</thead>

View File

@@ -146,6 +146,12 @@
<label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Notes">
@@ -188,6 +194,7 @@
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" 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 flex-grow-1 flex-shrink-1 text-truncate" 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 text-truncate" 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 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
@@ -206,6 +213,7 @@
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" 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 flex-grow-1 flex-shrink-1 text-truncate" 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 text-truncate" 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 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", gasRecord.Files)</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)
{
@@ -254,6 +262,8 @@
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>

View File

@@ -109,5 +109,8 @@
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>

View File

@@ -78,6 +78,12 @@
<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='attachments' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</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>
@@ -118,6 +124,7 @@
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@translator.Translate(userLanguage, "Initial Odometer")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" onclick="toggleSort('odometer-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Distance")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
@@ -131,8 +138,9 @@
<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 flex-grow-1 text-truncate" data-column="date">@odometerRecord.Date.ToShortDateString()</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@odometerRecord.InitialMileage</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@odometerRecord.Mileage</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer" data-record-type="cost">@odometerRecord.Mileage</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" data-record-type="distance">@(odometerRecord.DistanceTraveled == default ? "---" : odometerRecord.DistanceTraveled)</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", odometerRecord.Files)</td>
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -181,6 +189,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>

View File

@@ -127,5 +127,8 @@
<li><hr class="context-menu-move move-header dropdown-divider"></li>
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate-vehicle" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="context-menu-move move-header dropdown-divider"></li>
<li><a class="dropdown-item context-menu-move move-header context-menu-print-tab-sticker" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="context-menu-move move-header dropdown-divider"></li>
<li><a class="dropdown-item context-menu-move move-header text-danger context-menu-delete" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>

View File

@@ -82,9 +82,9 @@
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
</select>
<label for="reminderRecurringMonth">@translator.Translate(userLanguage, "Month")</label>
<label for="reminderRecurringMonth">@translator.Translate(userLanguage, "Time")</label>
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Date || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
<!option value="Other" @(Model.ReminderMonthInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.ReminderMonthInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="Other" @(Model.ReminderMonthInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.ReminderMonthInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval} {Model.CustomMonthIntervalUnit}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="OneMonth" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage, "1 Month")</!option>
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage,"3 Months")</!option>
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage,"6 Months")</!option>
@@ -136,6 +136,7 @@
<script>
var customMileageInterval = @Model.CustomMileageInterval;
var customMonthInterval = @Model.CustomMonthInterval;
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
function getReminderRecordModelData() {
return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')}
}

View File

@@ -140,7 +140,7 @@
<span class="badge text-bg-success">@translator.Translate(userLanguage, "Not Urgent")</span>
}
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" data-column="metric">
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" data-column="metric" data-record-type="cost">
@if (reminderRecord.Metric == ReminderMetric.Date)
{
@reminderRecord.Date.ToShortDateString()
@@ -200,6 +200,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>

View File

@@ -168,7 +168,6 @@
</div>
</div>
}
<div id="vehicleHistoryReport" class="showOnPrint"></div>
<script>
getSelectedMetrics();

View File

@@ -78,6 +78,12 @@
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Notes" checked>
@@ -118,6 +124,7 @@
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
@@ -133,6 +140,7 @@
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(serviceRecord.Mileage == default ? "---" : serviceRecord.Mileage.ToString())</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@serviceRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(serviceRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", serviceRecord.Files)</td>
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1" data-column="notes">@StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -183,6 +191,8 @@
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>

View File

@@ -0,0 +1,277 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model StickerViewModel
@{
var userConfig = config.GetUserConfig(User);
var hideZero = userConfig.HideZero;
var userLanguage = userConfig.UserLanguage;
}
@if( Model.ReminderRecords.Any()){
@foreach(ReminderRecord reminder in Model.ReminderRecords){
<div class="reminderSticker">
<div class="row justify-content-center mt-2">
<img src="@config.GetLogoUrl()" class="lubelogger-logo-sticker" />
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-1">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2 text-uppercase fw-bold">@($"{reminder.Description}")</p>
</div>
</div>
@if (reminder.Metric == ReminderMetric.Odometer || reminder.Metric == ReminderMetric.Both)
{
<div class="row">
<div class="col-12 text-center">
<p class="display-2">@($"{translator.Translate(userLanguage, "Odometer")}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2 fw-bold">@($"{reminder.Mileage}")</p>
</div>
</div>
}
@if (reminder.Metric == ReminderMetric.Date || reminder.Metric == ReminderMetric.Both)
{
<div class="row">
<div class="col-12 text-center">
<p class="display-2">@($"{translator.Translate(userLanguage, "Date")}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2 fw-bold">@($"{reminder.Date.ToShortDateString()}")</p>
</div>
</div>
}
@if (reminder.Metric == ReminderMetric.Both)
{
<div class="row">
<div class="col-12 text-center">
<p class="display-2 text-uppercase">@($"{translator.Translate(userLanguage, "Whichever comes first")}")</p>
</div>
</div>
}
</div>
}
} else if (Model.GenericRecords.Any()){
@foreach(GenericRecord genericRecord in Model.GenericRecords){
<div class="d-flex flex-column recordSticker">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
</div>
<hr />
<div class="row">
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
<li class="list-group-item">
<span class="lead">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</span>
</li>
@foreach (ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
</ul>
</div>
<div class="col-6">
<ul class="list-group">
@if(!string.IsNullOrWhiteSpace(genericRecord.Description)){
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {genericRecord.Description}")
</li>
}
@switch(Model.RecordType){
case ImportMode.ServiceRecord:
case ImportMode.RepairRecord:
case ImportMode.UpgradeRecord:
case ImportMode.GasRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Odometer")}: {genericRecord.Mileage}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {genericRecord.Cost.ToString("C")}")
</li>
break;
case ImportMode.TaxRecord:
case ImportMode.PlanRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {genericRecord.Cost.ToString("C")}")
</li>
break;
case ImportMode.OdometerRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Odometer")}: {genericRecord.Mileage}")
</li>
break;
}
@foreach(ExtraField extraField in genericRecord.ExtraFields){
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
</div>
<hr />
@if(genericRecord.RequisitionHistory.Any()){
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Quantity")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Cost")</th>
</tr>
</thead>
<tbody>
@foreach (SupplyUsageHistory usageHistory in genericRecord.RequisitionHistory)
{
<tr class="d-flex">
<td class="col-2 text-truncate">@usageHistory.PartNumber</td>
<td class="col-6 text-truncate">@usageHistory.Description</td>
<td class="col-2">@usageHistory.Quantity.ToString("F")</td>
<td class="col-2">@usageHistory.Cost.ToString("C2")</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<hr />
}
<div class="row flex-grow-1 flex-shrink-1">
<div class="col-12">
<div class="stickerNote ms-1 me-1 p-1">
@(genericRecord.Notes)
</div>
</div>
</div>
</div>
}
<script>setMarkDownStickerNotes()</script>
} else if (Model.SupplyRecords.Any()){
@foreach (SupplyRecord supplyRecord in Model.SupplyRecords)
{
<div class="d-flex flex-column recordSticker">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
</div>
<hr />
<div class="row">
@if(Model.VehicleData.Id != default){
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
<li class="list-group-item">
<span class="lead">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</span>
</li>
@foreach (ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
</ul>
</div>
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {supplyRecord.Description}")
</li>
@if(!string.IsNullOrWhiteSpace(supplyRecord.PartNumber)){
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Part Number")}: {supplyRecord.PartNumber}")
</li>
}
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Supplier/Vendor")}: {supplyRecord.PartSupplier}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {supplyRecord.Cost.ToString("C")}")
</li>
@foreach (ExtraField extraField in supplyRecord.ExtraFields)
{
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
} else {
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {supplyRecord.Description}")
</li>
@if (!string.IsNullOrWhiteSpace(supplyRecord.PartNumber))
{
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Part Number")}: {supplyRecord.PartNumber}")
</li>
}
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Supplier/Vendor")}: {supplyRecord.PartSupplier}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {supplyRecord.Cost.ToString("C")}")
</li>
</ul>
</div>
<div class="col-6">
<ul class="list-group">
@foreach (ExtraField extraField in supplyRecord.ExtraFields)
{
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
}
</div>
<hr />
<div class="row flex-grow-1 flex-shrink-1">
<div class="col-12">
<div class="stickerNote ms-1 me-1 p-1">
@(supplyRecord.Notes)
</div>
</div>
</div>
</div>
}
<script>setMarkDownStickerNotes()</script>
}

View File

@@ -90,6 +90,12 @@
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Notes" checked>
@@ -132,6 +138,7 @@
<th scope="col" class="col-2 flex-grow-1 col-xl-3 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" 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 text-truncate" 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 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
@@ -149,6 +156,7 @@
<td class="col-2 flex-grow-1 col-xl-3 text-truncate" data-column="description">@supplyRecord.Description</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="quantity">@supplyRecord.Quantity</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(supplyRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", supplyRecord.Files)</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -194,6 +202,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())

View File

@@ -57,9 +57,9 @@
<input class="form-check-input" type="checkbox" onChange="enableTaxRecurring()" role="switch" id="taxIsRecurring" checked="@Model.IsRecurring">
<label class="form-check-label" for="taxIsRecurring">@translator.Translate(userLanguage,"Is Recurring")</label>
</div>
<label for="taxRecurringMonth">@translator.Translate(userLanguage,"Month")</label>
<label for="taxRecurringMonth">@translator.Translate(userLanguage,"Time")</label>
<select class="form-select" onchange="checkCustomMonthIntervalForTax()" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
<!option value="Other" @(Model.RecurringInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.RecurringInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="Other" @(Model.RecurringInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.RecurringInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval} {Model.CustomMonthIntervalUnit}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="OneMonth" @(Model.RecurringInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage,"1 Month")</!option>
<!option value="ThreeMonths" @(Model.RecurringInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage, "3 Months")</!option>
<!option value="SixMonths" @(Model.RecurringInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage, "6 Months")</!option>
@@ -116,6 +116,7 @@
<script>
var uploadedFiles = [];
var customMonthInterval = @Model.CustomMonthInterval;
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
var recurringReminderRecordId = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {

View File

@@ -72,6 +72,12 @@
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Notes" checked>
@@ -111,6 +117,7 @@
<th scope="col" class="col-3 flex-grow-1 col-xl-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-4 flex-grow-1 col-xl-6 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('tax-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
@@ -125,6 +132,7 @@
<td class="col-3 flex-grow-1 col-xl-1 text-truncate" data-column="date">@taxRecord.Date.ToShortDateString()</td>
<td class="col-4 flex-grow-1 col-xl-6 text-truncate" data-column="description">@taxRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(taxRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", taxRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(taxRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -170,6 +178,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())

View File

@@ -78,6 +78,12 @@
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Notes" checked>
@@ -118,6 +124,7 @@
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 col-xl-4 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('upgrade-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
@@ -133,6 +140,7 @@
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(upgradeRecord.Mileage == default ? "---" : upgradeRecord.Mileage.ToString())</td>
<td class="col-3 flex-grow-1 flex-shrink-1 col-xl-4 text-truncate" data-column="description">@upgradeRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(upgradeRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", upgradeRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
@@ -184,6 +192,8 @@
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>

View File

@@ -8,7 +8,7 @@
var userLanguage = userConfig.UserLanguage;
var extraFields = Model.ReportParameters.ExtraFields;
}
<div class="vehicleDetailTabContainer">
<div>
<div class="row mt-2">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />

View File

@@ -8,13 +8,7 @@ services:
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:
@@ -35,12 +29,6 @@ services:
- /etc/localtime:/etc/localtime:ro
volumes:
config:
data:
translations:
documents:
images:
temp:
log:
keys:
postgres:

View File

@@ -8,13 +8,7 @@ services:
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:
@@ -40,13 +34,7 @@ services:
traefik.http.services.whoami.loadbalancer.server.port: 5000
volumes:
config:
data:
translations:
documents:
images:
temp:
log:
keys:
networks:

View File

@@ -8,13 +8,7 @@ services:
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:
@@ -23,11 +17,5 @@ services:
- .env
volumes:
config:
data:
translations:
documents:
images:
temp:
log:
keys:

View File

@@ -49,9 +49,13 @@
<input type="text" id="inputFileExtensions" class="form-control">
<small class="text-body-secondary">Blank for default, * for all files</small>
</div>
<div class="form-check form-switch">
<input class="form-check-input"type="checkbox" role="switch" id="inputCustomWidgets">
<label class="form-check-label" for="inputCustomWidgets">Custom Widgets</label>
</div>
</div>
<div class="col-6">
<div class="form-group">
<div class="form-group">
<label for="inputCustomLogo">Custom Logo URL</label>
<input type="text" id="inputCustomLogo" class="form-control">
<small class="text-body-secondary">Default size: 204x48</small>
@@ -66,6 +70,10 @@
<input type="text" id="inputWebHook" class="form-control">
<small class="text-body-secondary">URL to WebHook Consumer</small>
</div>
<div class="form-check form-switch">
<input class="form-check-input"type="checkbox" role="switch" id="inputInvariantAPI">
<label class="form-check-label" for="inputInvariantAPI">Invariant API</label>
</div>
</div>
</div>
</div>
@@ -262,6 +270,12 @@ function generateConfig(){
if ($("#inputPostgres").val().trim() != ''){
windowConfig["POSTGRES_CONNECTION"]=$("#inputPostgres").val();
}
if ($("#inputCustomWidgets").is(":checked")){
windowConfig["LUBELOGGER_CUSTOM_WIDGETS"]=$('#inputCustomWidgets').is(':checked');
}
if ($("#inputInvariantAPI").is(":checked")){
windowConfig["LUBELOGGER_INVARIANT_API"]=$('#inputInvariantAPI').is(':checked');
}
if ($('#inputSmtpServer').val().trim() != ''){
windowConfig["MailConfig"] = {
EmailServer: $("#inputSmtpServer").val(),
@@ -327,6 +341,12 @@ function generateConfig(){
if ($("#inputPostgres").val().trim() != ''){
dockerConfig.push(`POSTGRES_CONNECTION="${$('#inputPostgres').val()}"`);
}
if ($("#inputCustomWidgets").is(":checked")){
dockerConfig.push(`LUBELOGGER_CUSTOM_WIDGETS="${$('#inputCustomWidgets').is(':checked')}"`);
}
if ($("#inputInvariantAPI").is(":checked")){
dockerConfig.push(`LUBELOGGER_INVARIANT_API="${$('#inputInvariantAPI').is(':checked')}"`);
}
if ($('#inputSmtpServer').val().trim() != ''){
dockerConfig.push(`MailConfig__EmailServer="${$('#inputSmtpServer').val()}"`);
dockerConfig.push(`MailConfig__EmailFrom="${$('#inputSmtpFrom').val()}"`);

View File

@@ -81,6 +81,34 @@ html {
color: #000 !important;
overflow: visible;
}
.stickerPrintContainer {
background-color: #fff !important;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
margin: 0;
font-size: 14px;
line-height: 18px;
color: #000 !important;
overflow: visible;
z-index: 1030;
}
.reminderSticker {
width: 98%;
aspect-ratio: 1/1;
border-style: dashed;
border-width: 2px;
page-break-after: always;
}
.recordSticker {
height: 100%;
page-break-after: always;
}
.stickerNote {
height:100%;
}
body {
background-color: #fff !important;
@@ -478,6 +506,12 @@ html[data-bs-theme="light"] .api-method:hover {
object-fit: scale-down;
pointer-events: none;
}
.lubelogger-logo-sticker {
height: 6rem;
width: auto;
object-fit: scale-down;
pointer-events: none;
}
::-ms-reveal {
display: none;
@@ -486,4 +520,12 @@ html[data-bs-theme="light"] .api-method:hover {
.lubelogger-report-banner {
border-top: thin solid black;
text-align: center;
}
.attachment-badge-xs {
padding-left: 0.25em;
padding-right: 0.25em;
font-size: 0.6em;
font-weight: 500;
top: 15%
}

File diff suppressed because one or more lines are too long

View File

@@ -33,15 +33,7 @@ td, th {
<h2>{VehicleInformation}</h2>
<table>
<tr>
<th>
Urgency
</th>
<th>
Description
</th>
<th>
Due
</th>
{TableHeader}
</tr>
{TableBody}
</table>

View File

@@ -183,7 +183,7 @@ function hidePinnedNotes(vehicleId) {
}
}
function filterGarage(sender, isSort) {
function filterGarage(sender) {
var rowData = $(".garage-item");
if (sender == undefined) {
rowData.removeClass('override-hide');
@@ -191,14 +191,9 @@ function filterGarage(sender, isSort) {
}
var tagName = sender.textContent;
if ($(sender).hasClass("bg-primary")) {
if (!isSort) {
rowData.removeClass('override-hide');
$(sender).removeClass('bg-primary');
$(sender).addClass('bg-secondary');
} else {
rowData.addClass('override-hide');
$(`[data-tags~='${tagName}']`).removeClass('override-hide');
}
rowData.removeClass('override-hide');
$(sender).removeClass('bg-primary');
$(sender).addClass('bg-secondary');
} else {
//hide table rows.
rowData.addClass('override-hide');
@@ -264,14 +259,13 @@ function sortGarage(sender, isMobile) {
//restore table
sender.removeClass('sort-desc');
sender.html(isMobile ? `<span class="ms-2 display-3">${garageIcon}${sortColumn}</span>` : `${garageIcon}${sortColumn}`);
$('.vehiclesContainer').html(storedTableRowState);
filterGarage($(".tagfilter.bg-primary").get(0), true);
resetSortGarage();
} else {
//first time sorting.
//check if table was sorted before by a different column(only relevant to fuel tab)
if (storedTableRowState != null && ($(".sort-asc").length > 0 || $(".sort-desc").length > 0)) {
if ($("[default-sort]").length > 0 && ($(".sort-asc").length > 0 || $(".sort-desc").length > 0)) {
//restore table state.
$('.vehiclesContainer').html(storedTableRowState);
resetSortGarage();
//reset other sorted columns
if ($(".sort-asc").length > 0) {
$(".sort-asc").html($(".sort-asc").html().replace(sortAscIcon, ""));
@@ -284,12 +278,28 @@ function sortGarage(sender, isMobile) {
}
sender.addClass('sort-asc');
sender.html(isMobile ? `<span class="ms-2 display-3">${garageIcon}${sortColumn}${sortAscIcon}</span>` : `${garageIcon}${sortColumn}${sortAscIcon}`);
storedTableRowState = null;
storedTableRowState = $('.vehiclesContainer').html();
//append sortRowId to the vehicle container
if ($("[default-sort]").length == 0) {
$(`.garage-item`).map((index, elem) => {
$(elem).attr("default-sort", index);
});
}
sortVehicles(false);
}
}
}
function resetSortGarage() {
var rowData = $(`.garage-item`);
var sortedRow = rowData.toArray().sort((a, b) => {
var currentVal = $(a).attr('default-sort');
var nextVal = $(b).attr('default-sort');
return currentVal - nextVal;
});
$(".garage-item-add").map((index, elem) => {
sortedRow.push(elem);
})
$(`.vehiclesContainer`).html(sortedRow);
}
let dragged = null;
let draggedId = 0;

View File

@@ -67,4 +67,31 @@ function remoteLogin() {
window.location.href = data;
}
})
}
function sendRegistrationToken() {
Swal.fire({
title: 'Please Provide an Email Address',
html: `
<input type="text" id="inputTokenEmail" class="swal2-input" placeholder="Email Address" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Send',
focusConfirm: false,
preConfirm: () => {
const tokenEmail = $("#inputTokenEmail").val();
if (!tokenEmail || tokenEmail.trim() == '') {
Swal.showValidationMessage(`Please enter a valid email address`);
}
return { tokenEmail }
},
}).then(function (result) {
if (result.isConfirmed) {
$.post('/Login/SendRegistrationToken', { emailAddress: result.value.tokenEmail }, function (data) {
if (data.success) {
successToast(data.message);
} else {
errorToast(data.message);
}
});
}
});
}

View File

@@ -139,7 +139,7 @@ function getAndValidateOdometerRecordValues() {
function recalculateDistance() {
//force distance recalculation
//reserved for when data is incoherent with negative distances due to non-chronologica order of odometer records.
//reserved for when data is incoherent with negative distances due to non-chronological order of odometer records.
var vehicleId = GetVehicleId().vehicleId
$.post(`/Vehicle/ForceRecalculateDistanceByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {
@@ -362,15 +362,16 @@ function getRecordedOdometer() {
return parseFloat(`${recordedOdometer}.${recordedSubOdometer}`);
}
function saveRecordedOdometer() {
//update current odometer value
$("#odometerRecordMileage").val(parseInt(getRecordedOdometer()).toString());
//save coordinates into a CSV file and upload
if (tripCoordinates.length > 0) {
if (tripCoordinates.length > 1) {
//update current odometer value
$("#odometerRecordMileage").val(parseInt(getRecordedOdometer()).toString());
//generate attachment
$.post('/Files/UploadCoordinates', { coordinates: tripCoordinates }, function (response) {
uploadedFiles.push(response);
$.post('/Vehicle/GetFilesPendingUpload', { uploadedFiles: uploadedFiles }, function (viewData) {
$("#filesPendingUpload").html(viewData);
tripCoordinates = [];
tripCoordinates = ["Latitude,Longitude"];
});
});
}

View File

@@ -362,6 +362,9 @@ function configurePlanTableContextMenu(planRecordId, currentSwimLane) {
deletePlanRecord(planRecordId, true);
});
let planRecordIdArray = [planRecordId];
$(".context-menu-print-tab-sticker").on('click', () => {
printTabStickers(planRecordIdArray, 'PlanRecord');
});
$(".context-menu-duplicate").on('click', () => {
duplicateRecords(planRecordIdArray, 'PlanRecord');
});

View File

@@ -21,23 +21,29 @@ function checkCustomMonthInterval() {
if (selectedValue == "Other") {
$("#workAroundInput").show();
Swal.fire({
title: 'Specify Custom Month Interval',
title: 'Specify Custom Time Interval',
html: `
<input type="text" inputmode="numeric" id="inputCustomMileage" class="swal2-input" placeholder="Months" onkeydown="handleSwalEnter(event)">
<input type="text" inputmode="numeric" id="inputCustomMonth" class="swal2-input" placeholder="Time" onkeydown="handleSwalEnter(event)">
<select class="swal2-select" id="inputCustomMonthUnit">
<option value="Months">Months</option>
<option value="Days">Days</option>
</select>
`,
confirmButtonText: 'Set',
focusConfirm: false,
preConfirm: () => {
const customMonth = $("#inputCustomMileage").val();
const customMonth = $("#inputCustomMonth").val();
if (!customMonth || isNaN(parseInt(customMonth)) || parseInt(customMonth) <= 0) {
Swal.showValidationMessage(`Please enter a valid number`);
}
return { customMonth }
const customMonthUnit = $("#inputCustomMonthUnit").val();
return { customMonth, customMonthUnit }
},
}).then(function (result) {
if (result.isConfirmed) {
customMonthInterval = result.value.customMonth;
$("#reminderRecurringMonth > option[value='Other']").text(`Other: ${result.value.customMonth}`);
customMonthIntervalUnit = result.value.customMonthUnit;
$("#reminderRecurringMonth > option[value='Other']").text(`Other: ${result.value.customMonth} ${result.value.customMonthUnit}`);
} else {
$("#reminderRecurringMonth").val(getReminderRecordModelData().monthInterval);
}
@@ -276,6 +282,7 @@ function getAndValidateReminderRecordValues() {
reminderMonthInterval: reminderRecurringMonth,
customMileageInterval: customMileageInterval,
customMonthInterval: customMonthInterval,
customMonthIntervalUnit: customMonthIntervalUnit,
tags: reminderTags
}
}

View File

@@ -114,10 +114,7 @@ function generateVehicleHistoryReport() {
reportParameter: result.value.selectedColumnsData
}, function (data) {
if (data) {
$("#vehicleHistoryReport").html(data);
setTimeout(function () {
window.print();
}, 500);
printContainer(data);
}
})
}
@@ -278,6 +275,13 @@ function toggleBarChartTableData() {
$('[report-data="cost"]').removeClass('d-none');
}
}
function toggleCostTableHint() {
if ($(".cost-table-hint").hasClass("d-none")) {
$(".cost-table-hint").removeClass("d-none");
} else {
$(".cost-table-hint").addClass("d-none");
}
}
function updateReminderPie() {
var vehicleId = GetVehicleId().vehicleId;
var daysToAdd = $("#reminderOption").val();

View File

@@ -362,7 +362,23 @@ function initTagSelector(input, noDataList) {
input.tagsinput();
}
}
function getAndValidateSelectedVehicle() {
var selectedVehiclesArray = [];
$("#vehicleSelector :checked").map(function () {
selectedVehiclesArray.push(this.value);
});
if (selectedVehiclesArray.length == 0) {
return {
hasError: true,
ids: []
}
} else {
return {
hasError: false,
ids: selectedVehiclesArray
}
}
}
function showMobileNav() {
$(".lubelogger-mobile-nav").addClass("lubelogger-mobile-nav-show");
}
@@ -396,7 +412,6 @@ function setDebounce(callBack) {
callBack();
}, 1000);
}
var storedTableRowState = null;
function toggleSort(tabName, sender) {
var sortColumn = sender.textContent;
var sortAscIcon = '<i class="bi bi-sort-numeric-down ms-2"></i>';
@@ -412,14 +427,13 @@ function toggleSort(tabName, sender) {
//restore table
sender.removeClass('sort-desc');
sender.html(`${sortColumn}`);
$(`#${tabName} table tbody`).html(storedTableRowState);
filterTable(tabName, $(".tagfilter.bg-primary").get(0), true);
resetSortTable(tabName);
} else {
//first time sorting.
//check if table was sorted before by a different column(only relevant to fuel tab)
if (storedTableRowState != null && ($(".sort-asc").length > 0 || $(".sort-desc").length > 0)) {
if ($("[default-sort]").length > 0 && ($(".sort-asc").length > 0 || $(".sort-desc").length > 0)) {
//restore table state.
$(`#${tabName} table tbody`).html(storedTableRowState);
resetSortTable(tabName);
//reset other sorted columns
if ($(".sort-asc").length > 0) {
$(".sort-asc").html($(".sort-asc").html().replace(sortAscIcon, ""));
@@ -432,8 +446,12 @@ function toggleSort(tabName, sender) {
}
sender.addClass('sort-asc');
sender.html(`${sortColumn}${sortAscIcon}`);
storedTableRowState = null;
storedTableRowState = $(`#${tabName} table tbody`).html();
//append sortRowId to the table rows if nothing has been appended yet.
if ($("[default-sort]").length == 0) {
$(`#${tabName} table tbody tr`).map((index, elem) => {
$(elem).attr("default-sort", index);
});
}
sortTable(tabName, sortColumn, false);
}
}
@@ -453,9 +471,17 @@ function sortTable(tabName, columnName, desc) {
}
});
$(`#${tabName} table tbody`).html(sortedRow);
filterTable(tabName, $(".tagfilter.bg-primary").get(0), true);
}
function filterTable(tabName, sender, isSort) {
function resetSortTable(tabName) {
var rowData = $(`#${tabName} table tbody tr`);
var sortedRow = rowData.toArray().sort((a, b) => {
var currentVal = $(a).attr('default-sort');
var nextVal = $(b).attr('default-sort');
return currentVal - nextVal;
});
$(`#${tabName} table tbody`).html(sortedRow);
}
function filterTable(tabName, sender) {
var rowData = $(`#${tabName} table tbody tr`);
if (sender == undefined) {
rowData.removeClass('override-hide');
@@ -464,16 +490,10 @@ function filterTable(tabName, sender, isSort) {
var tagName = sender.textContent;
//check for other applied filters
if ($(sender).hasClass("bg-primary")) {
if (!isSort) {
rowData.removeClass('override-hide');
$(sender).removeClass('bg-primary');
$(sender).addClass('bg-secondary');
updateAggregateLabels();
} else {
rowData.addClass('override-hide');
$(`[data-tags~='${tagName}']`).removeClass('override-hide');
updateAggregateLabels();
}
rowData.removeClass('override-hide');
$(sender).removeClass('bg-primary');
$(sender).addClass('bg-secondary');
updateAggregateLabels();
} else {
//hide table rows.
rowData.addClass('override-hide');
@@ -608,6 +628,16 @@ function toggleMarkDownOverlay(textAreaName) {
textArea.parent().children(`label[for=${textAreaName}]`).append(overlayDiv);
}
}
function setMarkDownStickerNotes() {
var stickerContainers = $(".stickerNote");
if (stickerContainers.length > 0) {
stickerContainers.map((index, elem) => {
let originalStickerNote = $(elem).html().trim();
let markDownStickerNote = markdown(originalStickerNote);
$(elem).html(markDownStickerNote);
});
}
}
function showLinks(e) {
var textAreaName = $(e.parentElement).attr("for");
toggleMarkDownOverlay(textAreaName);
@@ -617,6 +647,33 @@ function printTab() {
window.print();
}, 500);
}
function printContainer(htmlData) {
$(".vehicleDetailTabContainer").addClass("hideOnPrint");
$(".stickerPrintContainer").addClass("showOnPrint");
$(".stickerPrintContainer").removeClass("hideOnPrint");
$(".stickerPrintContainer").html(htmlData);
setTimeout(function () {
window.print();
setTimeout(function () {
$(".stickerPrintContainer").removeClass("showOnPrint");
$(".stickerPrintContainer").addClass("hideOnPrint");
$(".vehicleDetailTabContainer").removeClass("hideOnPrint");
$(".stickerPrintContainer").html("");
}, 1000);
}, 500);
}
function printTabStickers(ids, source) {
var vehicleId = GetVehicleId().vehicleId;
$.post('/Vehicle/PrintRecordStickers', {
vehicleId: vehicleId,
recordIds: ids,
importMode: source
}, function (data) {
if (data) {
printContainer(data);
}
})
}
function exportVehicleData(mode) {
var vehicleId = GetVehicleId().vehicleId;
$.get('/Vehicle/ExportFromVehicleToCsv', { vehicleId: vehicleId, mode: mode }, function (data) {
@@ -1245,8 +1302,11 @@ function replenishSupplies() {
currentCost = 0;
}
var currentQuantity = globalParseFloat($('#supplyRecordQuantity').val());
if (isNaN(currentQuantity)) {
currentQuantity = 0;
}
var newQuantity = currentQuantity + replenishedQuantity;
if (replenishedCost.trim() == '') {
if (replenishedCost.trim() == '' && currentCost > 0 && currentQuantity > 0) {
var unitCost = currentCost / currentQuantity;
var newCost = newQuantity * unitCost;
@@ -1389,7 +1449,7 @@ function handleModalPaste(e, recordType) {
}
function handleEnter(e) {
if ((event.ctrlKey || event.metaKey) && event.which == 13) {
var saveButton = $(e).parent().find(".modal-footer .btn-primary");
var saveButton = $(e).parent().find(".modal-footer .btn-primary:not('.d-none')");
if (saveButton.length > 0) {
saveButton.first().trigger('click');
}

View File

@@ -102,23 +102,29 @@ function checkCustomMonthIntervalForTax() {
if (selectedValue == "Other") {
$("#workAroundInput").show();
Swal.fire({
title: 'Specify Custom Month Interval',
title: 'Specify Custom Time Interval',
html: `
<input type="text" inputmode="numeric" id="inputCustomMileage" class="swal2-input" placeholder="Months" onkeydown="handleSwalEnter(event)">
<input type="text" inputmode="numeric" id="inputCustomMonth" class="swal2-input" placeholder="Months" onkeydown="handleSwalEnter(event)">
<select class="swal2-select" id="inputCustomMonthUnit">
<option value="Months">Months</option>
<option value="Days">Days</option>
</select>
`,
confirmButtonText: 'Set',
focusConfirm: false,
preConfirm: () => {
const customMonth = $("#inputCustomMileage").val();
const customMonth = $("#inputCustomMonth").val();
if (!customMonth || isNaN(parseInt(customMonth)) || parseInt(customMonth) <= 0) {
Swal.showValidationMessage(`Please enter a valid number`);
}
return { customMonth }
const customMonthUnit = $("#inputCustomMonthUnit").val();
return { customMonth, customMonthUnit }
},
}).then(function (result) {
if (result.isConfirmed) {
customMonthInterval = result.value.customMonth;
$("#taxRecurringMonth > option[value='Other']").text(`Other: ${result.value.customMonth}`);
customMonthIntervalUnit = result.value.customMonthUnit;
$("#taxRecurringMonth > option[value='Other']").text(`Other: ${result.value.customMonth} ${result.value.customMonthUnit}`);
} else {
$("#taxRecurringMonth").val(getTaxRecordModelData().monthInterval);
}
@@ -172,6 +178,7 @@ function getAndValidateTaxRecordValues() {
isRecurring: taxIsRecurring,
recurringInterval: taxRecurringMonth,
customMonthInterval: customMonthInterval,
customMonthIntervalUnit: customMonthIntervalUnit,
tags: taxTags,
files: uploadedFiles,
addReminderRecord: addReminderRecord,

View File

@@ -576,23 +576,6 @@ function showMultipleRemindersSelector() {
$("#recurringReminderInput").show();
}
}
function getAndValidateSelectedVehicle() {
var selectedVehiclesArray = [];
$("#vehicleSelector :checked").map(function () {
selectedVehiclesArray.push(this.value);
});
if (selectedVehiclesArray.length == 0) {
return {
hasError: true,
ids: []
}
} else {
return {
hasError: false,
ids: selectedVehiclesArray
}
}
}
function getAndValidateSelectedRecurringReminder() {
if ($("#multipleRemindersCheck").is(":checked")) {
//validate multiple reminders