Compare commits

..

36 Commits

Author SHA1 Message Date
Hargata Softworks
4a007530a6 Merge pull request #188 from hargata/Hargata/parse.url
added setting to automatically load parsed markdown.
2024-01-30 08:19:04 -07:00
DESKTOP-GENO133\IvanPlex
04ce448b23 removed onclick event since it ain;t helping. 2024-01-30 08:18:45 -07:00
DESKTOP-GENO133\IvanPlex
ccd446f299 added setting to automatically load parsed markdown. 2024-01-30 08:16:28 -07:00
Hargata Softworks
c49c8a5301 Merge pull request #186 from hargata/Hargata/parse.url
Hargata/parse.url
2024-01-29 21:05:24 -07:00
DESKTOP-GENO133\IvanPlex
55cc2819d0 show overlay in place instead. 2024-01-29 21:04:22 -07:00
DESKTOP-GENO133\IvanPlex
f8de7de0d6 added markdown for named notes. 2024-01-29 20:33:37 -07:00
Hargata Softworks
2ea1bc2c20 Merge pull request #185 from hargata/Hargata/parse.url
Added Markdown Parsing.
2024-01-29 19:53:38 -07:00
DESKTOP-GENO133\IvanPlex
90d095ea51 Updated version. 2024-01-29 19:50:01 -07:00
DESKTOP-GENO133\IvanPlex
e1d12d0918 added dependency 2024-01-29 19:47:45 -07:00
DESKTOP-GENO133\IvanPlex
d9d0957040 added method to parse markdown data. 2024-01-29 19:43:53 -07:00
Hargata Softworks
9d73db3c51 Merge pull request #184 from hargata/Hargata/reduce.login.error
added env variable to reduce auth-related logs.
2024-01-29 17:21:07 -07:00
DESKTOP-GENO133\IvanPlex
c0f0786fd4 added env variable to reduce auth-related logs. 2024-01-29 17:20:39 -07:00
Hargata Softworks
357eff116f Merge pull request #183 from hargata/Hargata/export.attachments
Export attachments for vehicle.
2024-01-29 13:33:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
32a047c522 added print function. 2024-01-29 13:32:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8a237bb7ec Export attachments for vehicle. 2024-01-29 13:24:30 -07:00
DESKTOP-GENO133\IvanPlex
f5b9072cc6 Updated Github pages. 2024-01-28 15:06:06 -07:00
Hargata Softworks
4e3eaa53ff Merge pull request #180 from hargata/Hargata/hr-mpg
Hargata/hr mpg
2024-01-28 07:39:42 -07:00
Hargata Softworks
4779d3f161 Merge pull request #181 from hargata/Hargata/move.records
Move records around.
2024-01-28 07:38:04 -07:00
DESKTOP-GENO133\IvanPlex
b0173fae94 added object inheritance. 2024-01-28 07:37:12 -07:00
DESKTOP-GENO133\IvanPlex
c6ee8830a3 added hours per gallon calculation. 2024-01-27 23:29:05 -07:00
DESKTOP-GENO133\IvanPlex
c2eeab5025 Merge commit '43fd40347f34bc7676bfd5531abf612e06285e5c' into Hargata/hr-mpg 2024-01-27 22:53:55 -07:00
DESKTOP-GENO133\IvanPlex
43fd40347f added engine hours variable. 2024-01-27 21:14:29 -07:00
Hargata Softworks
53139f9bb2 Merge pull request #179 from hargata/Hargata/sorting.supplies
added sorting in supplies tab.
2024-01-27 12:17:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0b6033cc00 added sorting in supplies tab. 2024-01-27 12:16:49 -07:00
Hargata Softworks
6f0115e5c5 Merge pull request #176 from hargata/Hargata/pinned.notes.garage
touch screen support for pinned notes.
2024-01-27 07:41:06 -07:00
Hargata Softworks
2403adf537 Merge pull request #177 from hargata/Hargata/sort.columns
make cost columns sortable.
2024-01-27 07:40:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f00ab897b5 make cost columns sortable. 2024-01-27 07:37:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
093631bf6f touch screen support for pinned notes. 2024-01-26 13:19:37 -07:00
Hargata Softworks
5c2835ab76 Merge pull request #175 from hargata/Hargata/pinned.notes.garage
added maxfilesize label and error handling.
2024-01-26 12:42:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
03029981fd added maxfilesize label and error handling. 2024-01-26 12:40:39 -07:00
Hargata Softworks
69b1838038 Merge pull request #174 from hargata/Hargata/pinned.notes.garage
display pinned notes in garage.
2024-01-26 12:02:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c2f83026c display pinned notes in garage. 2024-01-26 12:01:53 -07:00
Hargata Softworks
d36f2c59e3 Merge pull request #172 from hargata/Hargata/demo.stuff
clear temp files restoring demo instance.
2024-01-26 09:26:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
53ebec3f03 clear temp files restoring demo instance. 2024-01-26 09:25:44 -07:00
Hargata Softworks
f2cbbaeb12 Merge pull request #170 from hargata/Hargata/average.fuel.fix
include partial fuel up fuel consumption in average mpg.
2024-01-25 22:19:02 -07:00
DESKTOP-GENO133\IvanPlex
31c1202649 include partial fuel up fuel consumption in average mpg. 2024-01-25 22:18:33 -07:00
59 changed files with 899 additions and 166 deletions

3
.env
View File

@@ -5,4 +5,5 @@ MailConfig__EmailFrom=""
MailConfig__UseSSL="false"
MailConfig__Port=587
MailConfig__Username=""
MailConfig__Password=""
MailConfig__Password=""
LOGGING__LOGLEVEL__DEFAULT=Error

View File

@@ -493,7 +493,7 @@ namespace CarCareTracker.Controllers
[Route("/api/demo/restore")]
public IActionResult RestoreDemo()
{
var result = _fileHelper.RestoreBackup("/defaults/demo_default.zip");
var result = _fileHelper.RestoreBackup("/defaults/demo_default.zip", true);
return Json(result);
}
private int GetMaxMileage(int vehicleId)

View File

@@ -538,10 +538,13 @@ namespace CarCareTracker.Controllers
{
computedResults = computedResults.OrderByDescending(x => DateTime.Parse(x.Date)).ThenByDescending(x => x.Mileage).ToList();
}
var vehicleIsElectric = _dataAccess.GetVehicleById(vehicleId).IsElectric;
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
var viewModel = new GasRecordViewModelContainer()
{
UseKwh = vehicleIsElectric,
UseHours = vehicleUseHours,
GasRecords = computedResults
};
return PartialView("_Gas", viewModel);
@@ -564,9 +567,12 @@ namespace CarCareTracker.Controllers
return Json(result);
}
[HttpGet]
public IActionResult GetAddGasRecordPartialView()
public IActionResult GetAddGasRecordPartialView(int vehicleId)
{
return PartialView("_GasModal", new GasRecordInputContainer() { GasRecord = new GasRecordInput() });
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
return PartialView("_GasModal", new GasRecordInputContainer() { UseKwh = vehicleIsElectric, UseHours = vehicleUseHours, GasRecord = new GasRecordInput() });
}
[HttpGet]
public IActionResult GetGasRecordForEditById(int gasRecordId)
@@ -585,10 +591,13 @@ namespace CarCareTracker.Controllers
MissedFuelUp = result.MissedFuelUp,
Notes = result.Notes
};
var vehicleIsElectric = _dataAccess.GetVehicleById(convertedResult.VehicleId).IsElectric;
var vehicleData = _dataAccess.GetVehicleById(convertedResult.VehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
var viewModel = new GasRecordInputContainer()
{
UseKwh = vehicleIsElectric,
UseHours = vehicleUseHours,
GasRecord = convertedResult
};
return PartialView("_GasModal", viewModel);
@@ -972,6 +981,84 @@ namespace CarCareTracker.Controllers
return PartialView("_ReminderMakeUpReport", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetVehicleAttachments(int vehicleId, List<ImportMode> exportTabs)
{
List<GenericReportModel> attachmentData = new List<GenericReportModel>();
if (exportTabs.Contains(ImportMode.ServiceRecord)){
var records = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x=>x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.RepairRecord))
{
var records = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.UpgradeRecord))
{
var records = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.GasRecord))
{
var records = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.TaxRecord))
{
var records = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = 0,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.OdometerRecord))
{
var records = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (attachmentData.Any())
{
attachmentData = attachmentData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
var result = _fileHelper.MakeAttachmentsExport(attachmentData);
if (string.IsNullOrWhiteSpace(result))
{
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
}
return Json(new OperationResponse { Success = true, Message = result });
} else
{
return Json(new OperationResponse { Success = false, Message = "No Attachments Found" });
}
}
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult GetVehicleHistory(int vehicleId)
{
var vehicleHistory = new VehicleHistoryViewModel();
@@ -1311,6 +1398,14 @@ namespace CarCareTracker.Controllers
result = result.OrderByDescending(x => x.Pinned).ToList();
return PartialView("_Notes", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetPinnedNotesByVehicleId(int vehicleId)
{
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
result = result.Where(x=>x.Pinned).ToList();
return Json(result);
}
[HttpPost]
public IActionResult SaveNoteToVehicleId(Note note)
{
@@ -1601,5 +1696,55 @@ namespace CarCareTracker.Controllers
return Json(result);
}
#endregion
#region "Shared Methods"
public IActionResult MoveRecord(int recordId, ImportMode source, ImportMode destination)
{
var genericRecord = new GenericRecord();
bool result = false;
//get
switch (source)
{
case ImportMode.ServiceRecord:
genericRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
break;
case ImportMode.RepairRecord:
genericRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
break;
case ImportMode.UpgradeRecord:
genericRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
break;
}
//save
switch (destination)
{
case ImportMode.ServiceRecord:
result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(StaticHelper.GenericToServiceRecord(genericRecord));
break;
case ImportMode.RepairRecord:
result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(StaticHelper.GenericToRepairRecord(genericRecord));
break;
case ImportMode.UpgradeRecord:
result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(StaticHelper.GenericToUpgradeRecord(genericRecord));
break;
}
//delete
if (result)
{
switch (source)
{
case ImportMode.ServiceRecord:
_serviceRecordDataAccess.DeleteServiceRecordById(recordId);
break;
case ImportMode.RepairRecord:
_collisionRecordDataAccess.DeleteCollisionRecordById(recordId);
break;
case ImportMode.UpgradeRecord:
_upgradeRecordDataAccess.DeleteUpgradeRecordById(recordId);
break;
}
}
return Json(result);
}
#endregion
}
}

View File

@@ -94,6 +94,7 @@ namespace CarCareTracker.Helper
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
EnableAutoReminderRefresh = bool.Parse(_config[nameof(UserConfig.EnableAutoReminderRefresh)]),
EnableAutoOdometerInsert = bool.Parse(_config[nameof(UserConfig.EnableAutoOdometerInsert)]),

View File

@@ -1,4 +1,5 @@
using System.IO.Compression;
using CarCareTracker.Models;
using System.IO.Compression;
namespace CarCareTracker.Helper
{
@@ -8,7 +9,8 @@ namespace CarCareTracker.Helper
string MoveFileFromTemp(string currentFilePath, string newFolder);
bool DeleteFile(string currentFilePath);
string MakeBackup();
bool RestoreBackup(string fileName);
bool RestoreBackup(string fileName, bool clearExisting = false);
string MakeAttachmentsExport(List<GenericReportModel> exportData);
}
public class FileHelper : IFileHelper
{
@@ -38,7 +40,7 @@ namespace CarCareTracker.Helper
return string.Empty;
}
}
public bool RestoreBackup(string fileName)
public bool RestoreBackup(string fileName, bool clearExisting = false)
{
var fullFilePath = GetFullFilePath(fileName);
if (string.IsNullOrWhiteSpace(fullFilePath))
@@ -64,9 +66,17 @@ namespace CarCareTracker.Helper
{
Directory.CreateDirectory(existingPath);
}
else if (clearExisting)
{
var filesToDelete = Directory.GetFiles(existingPath);
foreach (string file in filesToDelete)
{
File.Delete(file);
}
}
//copy each files from temp folder to newPath
var filesToUpload = Directory.GetFiles(imagePath);
foreach(string file in filesToUpload)
foreach (string file in filesToUpload)
{
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
}
@@ -78,6 +88,14 @@ namespace CarCareTracker.Helper
{
Directory.CreateDirectory(existingPath);
}
else if (clearExisting)
{
var filesToDelete = Directory.GetFiles(existingPath);
foreach (string file in filesToDelete)
{
File.Delete(file);
}
}
//copy each files from temp folder to newPath
var filesToUpload = Directory.GetFiles(documentPath);
foreach (string file in filesToUpload)
@@ -100,12 +118,37 @@ namespace CarCareTracker.Helper
File.Move(configPath, StaticHelper.UserConfigPath, true);
}
return true;
} catch (Exception ex)
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error Restoring Database Backup: {ex.Message}");
return false;
}
}
public string MakeAttachmentsExport(List<GenericReportModel> exportData)
{
var folderName = Guid.NewGuid();
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
int fileIndex = 0;
foreach(GenericReportModel reportModel in exportData)
{
foreach(UploadedFiles file in reportModel.Files)
{
var fileToCopy = GetFullFilePath(file.Location);
var destFileName = $"{tempPath}/{fileIndex}{Path.GetExtension(file.Location)}";
File.Copy(fileToCopy, destFileName);
fileIndex++;
}
}
var destFilePath = $"{tempPath}.zip";
ZipFile.CreateFromDirectory(tempPath, destFilePath);
//delete temp directory
Directory.Delete(tempPath, true);
var zipFileName = $"/temp/{folderName}.zip";
return zipFileName;
}
public string MakeBackup()
{
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";

View File

@@ -11,12 +11,13 @@ namespace CarCareTracker.Helper
{
public string GetAverageGasMileage(List<GasRecordViewModel> results, bool useMPG)
{
var recordWithCalculatedMPG = results.Where(x => x.MilesPerGallon > 0);
var recordsToCalculateGallons = results.Where(x => x.MilesPerGallon > 0 || !x.IsFillToFull);
var recordWithCalculatedMPG = recordsToCalculateGallons.Where(x => x.MilesPerGallon > 0);
var minMileage = results.Min(x => x.Mileage);
if (recordWithCalculatedMPG.Any())
{
var maxMileage = recordWithCalculatedMPG.Max(x => x.Mileage);
var totalGallonsConsumed = recordWithCalculatedMPG.Sum(x => x.Gallons);
var totalGallonsConsumed = recordsToCalculateGallons.Sum(x => x.Gallons);
var deltaMileage = maxMileage - minMileage;
var averageGasMileage = (maxMileage - minMileage) / totalGallonsConsumed;
if (!useMPG)

View File

@@ -100,5 +100,45 @@ namespace CarCareTracker.Helper
new CostForVehicleByMonth { MonthId = 12, Cost = 0M}
};
}
public static ServiceRecord GenericToServiceRecord(GenericRecord input)
{
return new ServiceRecord
{
VehicleId = input.VehicleId,
Date = input.Date,
Description = input.Description,
Cost = input.Cost,
Mileage = input.Mileage,
Files = input.Files,
Notes = input.Notes
};
}
public static CollisionRecord GenericToRepairRecord(GenericRecord input)
{
return new CollisionRecord
{
VehicleId = input.VehicleId,
Date = input.Date,
Description = input.Description,
Cost = input.Cost,
Mileage = input.Mileage,
Files = input.Files,
Notes = input.Notes
};
}
public static UpgradeRecord GenericToUpgradeRecord(GenericRecord input)
{
return new UpgradeRecord
{
VehicleId = input.VehicleId,
Date = input.Date,
Description = input.Description,
Cost = input.Cost,
Mileage = input.Mileage,
Files = input.Files,
Notes = input.Notes
};
}
}
}

View File

@@ -1,14 +1,6 @@
namespace CarCareTracker.Models
{
public class CollisionRecord
public class CollisionRecord: GenericRecord
{
public int Id { get; set; }
public int VehicleId { get; set; }
public DateTime Date { get; set; }
public int Mileage { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@@ -2,7 +2,8 @@
{
public class GasRecordInputContainer
{
public bool UseKwh { get; set; }
public GasRecordInput GasRecord { get; set; }
public bool UseKwh { get; set; }
public bool UseHours { get; set; }
public GasRecordInput GasRecord { get; set; }
}
}

View File

@@ -2,7 +2,8 @@
{
public class GasRecordViewModelContainer
{
public bool UseKwh { get; set; }
public List<GasRecordViewModel> GasRecords { get; set; } = new List<GasRecordViewModel>();
public bool UseKwh { get; set; }
public bool UseHours { get; set; }
public List<GasRecordViewModel> GasRecords { get; set; } = new List<GasRecordViewModel>();
}
}

14
Models/GenericRecord.cs Normal file
View File

@@ -0,0 +1,14 @@
namespace CarCareTracker.Models
{
public class GenericRecord
{
public int Id { get; set; }
public int VehicleId { get; set; }
public DateTime Date { get; set; }
public int Mileage { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@@ -11,5 +11,6 @@
public string Description { get; set; }
public string Notes { get; set; }
public decimal Cost { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@@ -1,14 +1,6 @@
namespace CarCareTracker.Models
{
public class ServiceRecord
public class ServiceRecord: GenericRecord
{
public int Id { get; set; }
public int VehicleId { get; set; }
public DateTime Date { get; set; }
public int Mileage { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@@ -1,14 +1,6 @@
namespace CarCareTracker.Models
{
public class UpgradeRecord
public class UpgradeRecord: GenericRecord
{
public int Id { get; set; }
public int VehicleId { get; set; }
public DateTime Date { get; set; }
public int Mileage { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@@ -10,6 +10,7 @@
public bool HideZero { get; set; }
public bool UseUKMPG {get;set;}
public bool UseThreeDecimalGasCost { get; set; }
public bool UseMarkDownOnSavedNotes { get; set; }
public bool EnableAutoReminderRefresh { get; set; }
public bool EnableAutoOdometerInsert { get; set; }
public string UserNameHash { get; set; }

View File

@@ -9,5 +9,6 @@
public string Model { get; set; }
public string LicensePlate { get; set; }
public bool IsElectric { get; set; } = false;
public bool UseHours { get; set; } = false;
}
}

View File

@@ -24,6 +24,7 @@ Try it out before you download it! The live demo resets every 20 minutes.
- SweetAlert2
- CsvHelper
- Chart.js
- Drawdown
## Docker Setup (GHCR)
1. Install Docker

View File

@@ -4,7 +4,7 @@
{
foreach (Vehicle vehicle in Model)
{
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4">
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none" id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
<div class="card" onclick="viewVehicle(@vehicle.Id)">
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down;" />
<div class="card-body">

View File

@@ -35,6 +35,10 @@
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UseThreeDecimalGasCost">
<label class="form-check-label" for="useThreeDecimal">Use Three Decimals For Fuel Cost</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMarkDownOnSavedNotes" checked="@Model.UseMarkDownOnSavedNotes">
<label class="form-check-label" for="useMarkDownOnSavedNotes">Display Saved Notes in Markdown</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoReminderRefresh" checked="@Model.EnableAutoReminderRefresh">
<label class="form-check-label" for="enableAutoReminderRefresh">Auto Refresh Lapsed Recurring Reminders</label>
@@ -154,7 +158,7 @@
<img src="/defaults/lubelogger_logo.png" />
</div>
<div class="d-flex justify-content-center">
<small class="text-body-secondary">Version 1.0.8</small>
<small class="text-body-secondary">Version 1.1.0</small>
</div>
<p class="lead">
Proudly developed in the rural town of Price, Utah by Hargata Softworks.
@@ -187,6 +191,7 @@
<li class="list-group-item">SweetAlert2</li>
<li class="list-group-item">CsvHelper</li>
<li class="list-group-item">Chart.js</li>
<li class="list-group-item">Drawdown</li>
</ul>
</div>
</div>
@@ -211,6 +216,7 @@
hideZero: $("#hideZero").is(":checked"),
useUKMpg: $("#useUKMPG").is(":checked"),
useThreeDecimalGasCost: $("#useThreeDecimal").is(":checked"),
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
visibleTabs: visibleTabs,

View File

@@ -5,6 +5,7 @@
var userConfig = config.GetUserConfig(User);
var useDarkMode = userConfig.UseDarkMode;
var enableCsvImports = userConfig.EnableCsvImports;
var useMarkDown = userConfig.UseMarkDownOnSavedNotes;
var shortDatePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
var numberFormat = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
shortDatePattern = shortDatePattern.ToLower();
@@ -47,7 +48,8 @@
function getGlobalConfig() {
return {
useDarkMode : "@useDarkMode" == "True",
enableCsvImport : "@enableCsvImports" == "True"
enableCsvImport : "@enableCsvImports" == "True",
useMarkDown: "@useMarkDown" == "True"
}
}
function getShortDatePattern() {
@@ -59,10 +61,16 @@
//remove thousands separator.
var thousandSeparator = "@numberFormat.NumberGroupSeparator";
var decimalSeparator = "@numberFormat.NumberDecimalSeparator";
var currencySymbol = "@numberFormat.CurrencySymbol";
if (input == "---") {
input = "0";
}
//strip thousands from input.
input = input.replace(thousandSeparator, "");
//convert to JS format where decimal is only separated by .
input = input.replace(decimalSeparator, ".");
//remove any currency symbol
input = input.replace(currencySymbol, "");
return parseFloat(input);
}
function globalFloatToString(input) {

View File

@@ -21,6 +21,7 @@
<script src="~/js/planrecord.js" asp-append-version="true"></script>
<script src="~/js/odometerrecord.js" asp-append-version="true"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
<ul class="nav navbar-nav" id="vehicleTab" role="tablist">

View File

@@ -29,7 +29,7 @@
}
</div>
<div class="col-md-6 col-12">
<label for="collisionRecordNotes">Notes(optional)</label>
<label for="collisionRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="collisionRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
@@ -37,6 +37,7 @@
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="collisionRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
@@ -52,6 +53,7 @@
}
<label for="collisionRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>
@@ -61,7 +63,17 @@
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteCollisionRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteCollisionRecord(@Model.Id)">Delete</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">Move To</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'ServiceRecord')">Service Records</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'UpgradeRecord')">Upgrades</a></li>
</ul>
</div>
}
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">Cancel</button>
@if (isNew)

View File

@@ -22,6 +22,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('RepairRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">Export to CSV</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">Print</a></li>
</ul>
</div>
}
@@ -45,7 +47,7 @@
<th scope="col" class="col-2 col-xl-1">Date</th>
<th scope="col" class="col-2">Odometer</th>
<th scope="col" class="col-3 col-xl-4">Description</th>
<th scope="col" class="col-2">Cost</th>
<th scope="col" class="col-2" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">Cost</th>
<th scope="col" class="col-3">Notes</th>
</tr>
</thead>

View File

@@ -11,28 +11,29 @@
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var gasCostFormat = useThreeDecimals ? "C3" : "C2";
var useKwh = Model.UseKwh;
var useHours = Model.UseHours;
string consumptionUnit;
string fuelEconomyUnit;
string distanceUnit = useMPG ? "mi." : "km";
string distanceUnit = useHours ? "h" : (useMPG ? "mi." : "km");
if (useKwh)
{
consumptionUnit = "kWh";
fuelEconomyUnit = useMPG ? "mi./kWh" : "kWh/100km";
fuelEconomyUnit = useMPG ? $"{distanceUnit}/kWh" : $"kWh/100{distanceUnit}";
}
else if (useMPG && useUKMPG)
{
consumptionUnit = "imp gal";
fuelEconomyUnit = "mpg";
fuelEconomyUnit = useHours ? "h/g" : "mpg";
} else if (useUKMPG)
{
fuelEconomyUnit = "l/100mi.";
fuelEconomyUnit = useHours ? "l/100h" : "l/100mi.";
consumptionUnit = "l";
distanceUnit = "mi.";
distanceUnit = useHours ? "h" : "mi.";
}
else
{
consumptionUnit = useMPG ? "US gal" : "l";
fuelEconomyUnit = useMPG ? "mpg" : "l/100km";
fuelEconomyUnit = useHours ? (useMPG ? "h/g" : "l/100h") : (useMPG ? "mpg" : "l/100km");
}
}
<div class="row">
@@ -58,6 +59,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">Export to CSV</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">Print</a></li>
</ul>
</div>
} else {
@@ -77,10 +80,10 @@
<tr class="d-flex">
<th scope="col" class="col-2">Date Refueled</th>
<th scope="col" class="col-2">Odometer(@(distanceUnit))</th>
<th scope="col" class="col-2">Consumption(@(consumptionUnit))</th>
<th scope="col" class="col-4">Fuel Economy(@(fuelEconomyUnit))</th>
<th scope="col" class="col-1">Cost</th>
<th scope="col" class="col-1">Unit Cost</th>
<th scope="col" class="col-2" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">Consumption(@(consumptionUnit))</th>
<th scope="col" class="col-4" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">Fuel Economy(@(fuelEconomyUnit))</th>
<th scope="col" class="col-1" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">Cost</th>
<th scope="col" class="col-1" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">Unit Cost</th>
</tr>
</thead>
<tbody>

View File

@@ -5,6 +5,7 @@
var useMPG = config.GetUserConfig(User).UseMPG;
var useUKMPG = config.GetUserConfig(User).UseUKMPG;
var useKwh = Model.UseKwh;
var useHours = Model.UseHours;
var isNew = Model.GasRecord.Id == 0;
string consumptionUnit;
string distanceUnit;
@@ -19,7 +20,11 @@
{
consumptionUnit = useMPG ? "gallons" : "liters";
}
if (useUKMPG)
if (useHours)
{
distanceUnit = "hours";
}
else if (useUKMPG)
{
distanceUnit = "miles";
}
@@ -73,7 +78,7 @@
}
</div>
<div class="col-md-6 col-12">
<label for="gasRecordNotes">Notes(optional)</label>
<label for="gasRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="gasRecordNotes" class="form-control" rows="5">@Model.GasRecord.Notes</textarea>
@if (Model.GasRecord.Files.Any())
{
@@ -81,12 +86,14 @@
@await Html.PartialAsync("_UploadedFiles", Model.GasRecord.Files)
<label for="gasRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
{
<label for="gasRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>

View File

@@ -20,7 +20,7 @@
<input type="text" id="noteDescription" class="form-control" placeholder="Description of the note" value="@(isNew ? "" : Model.Description)">
</div>
<div class="col-12">
<label for="noteTextArea">Notes</label>
<label for="noteTextArea">Notes<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea class="form-control vehicleNoteContainer" id="noteTextArea">@Model.NoteText</textarea>
</div>
</div>

View File

@@ -29,7 +29,7 @@
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)">
@if (note.Pinned)
{
<td class="col-3"><i class='bi bi-pin-fill me-2'></i>@note.Description"</td>
<td class="col-3"><i class='bi bi-pin-fill me-2'></i>@note.Description</td>
} else
{
<td class="col-3">@note.Description</td>

View File

@@ -21,7 +21,7 @@
<input type="number" id="odometerRecordMileage" class="form-control" placeholder="Odometer reading" value="@(isNew ? "" : Model.Mileage)">
</div>
<div class="col-md-6 col-12">
<label for="odometerRecordNotes">Notes(optional)</label>
<label for="odometerRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="odometerRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
@@ -29,12 +29,14 @@
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="odometerRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
{
<label for="odometerRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>

View File

@@ -21,6 +21,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('OdometerRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('OdometerRecord')">Export to CSV</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">Print</a></li>
</ul>
</div>
}

View File

@@ -45,7 +45,7 @@
}
</div>
<div class="col-md-6 col-12">
<label for="planRecordNotes">Notes(optional)</label>
<label for="planRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="planRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
@@ -53,12 +53,14 @@
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="planRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
{
<label for="planRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>

View File

@@ -39,7 +39,7 @@
</div>
</div>
<div class="col-md-6 col-12">
<label for="reminderNotes">Notes(optional)</label>
<label for="reminderNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="reminderNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onChange="enableRecurring()" role="switch" id="reminderIsRecurring" checked="@Model.IsRecurring">

View File

@@ -1,90 +1,93 @@
@model ReportViewModel
<div class="container reportTabContainer">
<div class="row hideOnPrint">
<div class="col-md-3 col-12 mt-2">
<div class="row">
<div class="col-12">
<select class="form-select" id="yearOption" onchange="yearUpdated()">
<option value="0">All Time</option>
@foreach (int year in Model.Years)
{
<option value="@year">@year</option>
}
</select>
<div class="row hideOnPrint">
<div class="col-md-3 col-12 mt-2">
<div class="row">
<div class="col-12">
<select class="form-select" id="yearOption" onchange="yearUpdated()">
<option value="0">All Time</option>
@foreach (int year in Model.Years)
{
<option value="@year">@year</option>
}
</select>
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="costMakeUpReportContent">
@await Html.PartialAsync("_CostMakeUpReport", Model.CostMakeUpForVehicle)
</div>
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="costMakeUpReportContent">
@await Html.PartialAsync("_CostMakeUpReport", Model.CostMakeUpForVehicle)
</div>
</div>
</div>
<div class="col-md-6 col-12 mt-2">
<div class="row">
<div class="col-md-1 d-sm-none d-md-block"></div>
<div class="col-12 col-md-10 reportsCheckBoxContainer">
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="serviceExpenseCheck" value="1" checked>
<label class="form-check-label" for="serviceExpenseCheck">Service</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="repairExpenseCheck" value="2" checked>
<label class="form-check-label" for="repairExpenseCheck">Repairs</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="upgradeExpenseCheck" value="3" checked>
<label class="form-check-label" for="upgradeExpenseCheck">Upgrades</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="gasExpenseCheck" value="4" checked>
<label class="form-check-label" for="gasExpenseCheck">Gas</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="taxExpenseCheck" value="5" checked>
<label class="form-check-label" for="taxExpenseCheck">Tax</label>
</div>
</div>
<div class="col-md-6 col-12 mt-2">
<div class="row">
<div class="col-md-1 d-sm-none d-md-block"></div>
<div class="col-12 col-md-10 reportsCheckBoxContainer">
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="serviceExpenseCheck" value="1" checked>
<label class="form-check-label" for="serviceExpenseCheck">Service</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="repairExpenseCheck" value="2" checked>
<label class="form-check-label" for="repairExpenseCheck">Repairs</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="upgradeExpenseCheck" value="3" checked>
<label class="form-check-label" for="upgradeExpenseCheck">Upgrades</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="gasExpenseCheck" value="4" checked>
<label class="form-check-label" for="gasExpenseCheck">Fuel</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="taxExpenseCheck" value="5" checked>
<label class="form-check-label" for="taxExpenseCheck">Taxes</label>
</div>
</div>
<div class="col-md-1 d-sm-none d-md-block"></div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="gasCostByMonthReportContent">
@await Html.PartialAsync("_GasCostByMonthReport", Model.CostForVehicleByMonth)
</div>
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="gasCostByMonthReportContent">
@await Html.PartialAsync("_GasCostByMonthReport", Model.CostForVehicleByMonth)
<div class="col-md-3 col-12 mt-2">
<div class="row">
<div class="col-12">
<select class="form-select" onchange="updateReminderPie()" id="reminderOption">
<option value="0">As of Today</option>
<option value="30">+30 Days</option>
<option value="60">+60 Days</option>
<option value="90">+90 Days</option>
</select>
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="reminderMakeUpReportContent">
@await Html.PartialAsync("_ReminderMakeUpReport", Model.ReminderMakeUpForVehicle)
</div>
</div>
</div>
</div>
<div class="col-md-3 col-12 mt-2">
<div class="row">
<div class="col-12">
<select class="form-select" onchange="updateReminderPie()" id="reminderOption">
<option value="0">As of Today</option>
<option value="30">+30 Days</option>
<option value="60">+60 Days</option>
<option value="90">+90 Days</option>
</select>
<hr />
<div class="row hideOnPrint">
<div class="col-md-3 col-12 chartContainer" id="collaboratorContent">
@await Html.PartialAsync("_Collaborators", Model.Collaborators)
</div>
<div class="col-md-6 col-12 chartContainer">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="monthFuelMileageReportContent">
@await Html.PartialAsync("_MPGByMonthReport", Model.FuelMileageForVehicleByMonth)
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="reminderMakeUpReportContent">
@await Html.PartialAsync("_ReminderMakeUpReport", Model.ReminderMakeUpForVehicle)
<div class="col-md-3 col-12 chartContainer">
<div class="d-grid">
<button onclick="generateVehicleHistoryReport()" class="btn btn-secondary btn-md mt-1 mb-1">Vehicle Maintenance Report<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
<div class="d-grid">
<button onclick="exportAttachments()" class="btn btn-secondary btn-md mt-1 mb-1">Export Attachments<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
</div>
</div>
</div>
<hr />
<div class="row hideOnPrint">
<div class="col-md-3 col-12 chartContainer" id="collaboratorContent">
@await Html.PartialAsync("_Collaborators", Model.Collaborators)
</div>
<div class="col-md-6 col-12 chartContainer">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="monthFuelMileageReportContent">
@await Html.PartialAsync("_MPGByMonthReport", Model.FuelMileageForVehicleByMonth)
</div>
</div>
<div class="col-md-3 col-12 chartContainer">
<div class="d-flex justify-content-center">
<button onclick="generateVehicleHistoryReport()" class="btn btn-secondary btn-md mt-1 mb-1">Vehicle Maintenance Report<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
</div>
</div>
</div>
<div id="vehicleHistoryReport" class="showOnPrint"></div>

View File

@@ -29,7 +29,7 @@
}
</div>
<div class="col-md-6 col-12">
<label for="serviceRecordNotes">Notes(optional)</label>
<label for="serviceRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="serviceRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
@@ -37,6 +37,7 @@
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="serviceRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
@@ -52,6 +53,7 @@
}
<label for="serviceRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>
@@ -61,7 +63,17 @@
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteServiceRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteServiceRecord(@Model.Id)" >Delete</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">Move To</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'ServiceRecord', 'RepairRecord')">Repairs</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'ServiceRecord', 'UpgradeRecord')">Upgrades</a></li>
</ul>
</div>
}
<button type="button" class="btn btn-secondary" onclick="hideAddServiceRecordModal()">Cancel</button>
@if (isNew)

View File

@@ -22,6 +22,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('ServiceRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('ServiceRecord')">Export to CSV</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">Print</a></li>
</ul>
</div>
}
@@ -45,7 +47,7 @@
<th scope="col" class="col-2 col-xl-1">Date</th>
<th scope="col" class="col-2">Odometer</th>
<th scope="col" class="col-3 col-xl-4">Description</th>
<th scope="col" class="col-2">Cost</th>
<th scope="col" class="col-2" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">Cost</th>
<th scope="col" class="col-3">Notes</th>
</tr>
</thead>

View File

@@ -35,7 +35,7 @@
</div>
</div>
<div class="col-md-6 col-12">
<label for="supplyRecordNotes">Notes(optional)</label>
<label for="supplyRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="supplyRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
@@ -43,12 +43,14 @@
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="supplyRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
{
<label for="supplyRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>

View File

@@ -22,6 +22,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('SupplyRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('SupplyRecord')">Export to CSV</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">Print</a></li>
</ul>
</div>
}
@@ -46,8 +48,8 @@
<th scope="col" class="col-2">Part #</th>
<th scope="col" class="col-2">Supplier</th>
<th scope="col" class="col-2 col-xl-3">Description</th>
<th scope="col" class="col-1">Quantity</th>
<th scope="col" class="col-1">Cost</th>
<th scope="col" class="col-1" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">Quantity</th>
<th scope="col" class="col-1" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">Cost</th>
<th scope="col" class="col-2">Notes</th>
</tr>
</thead>

View File

@@ -23,7 +23,7 @@
<input type="text" id="taxRecordCost" class="form-control" placeholder="Cost of tax paid" value="@(isNew? "" : Model.Cost)">
</div>
<div class="col-md-6 col-12">
<label for="taxRecordNotes">Notes(optional)</label>
<label for="taxRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="taxRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onChange="enableTaxRecurring()" role="switch" id="taxIsRecurring" checked="@Model.IsRecurring">
@@ -45,6 +45,7 @@
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="taxRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="taxRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
@@ -60,6 +61,7 @@
}
<label for="taxRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="taxRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>

View File

@@ -22,6 +22,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('TaxRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('TaxRecord')">Export to CSV</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">Print</a></li>
</ul>
</div>
}
@@ -44,7 +46,7 @@
<tr class="d-flex">
<th scope="col" class="col-3 col-xl-1">Date</th>
<th scope="col" class="col-4 col-xl-6">Description</th>
<th scope="col" class="col-2">Cost</th>
<th scope="col" class="col-2" onclick="toggleSort('tax-tab-pane', this)" style="cursor:pointer;">Cost</th>
<th scope="col" class="col-3">Notes</th>
</tr>
</thead>

View File

@@ -29,7 +29,7 @@
}
</div>
<div class="col-md-6 col-12">
<label for="upgradeRecordNotes">Notes(optional)</label>
<label for="upgradeRecordNotes">Notes(optional)<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="upgradeRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
@@ -37,6 +37,7 @@
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="upgradeRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="upgradeRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
</div>
}
else
@@ -52,6 +53,7 @@
}
<label for="upgradeRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="upgradeRecordFiles">
<br /><small class="text-body-secondary">Max File Size: 28.6MB</small>
}
</div>
</div>
@@ -61,7 +63,17 @@
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteUpgradeRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteUpgradeRecord(@Model.Id)">Delete</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">Move To</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'UpgradeRecord', 'ServiceRecord')">Service Records</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'UpgradeRecord', 'RepairRecord')">Repairs</a></li>
</ul>
</div>
}
<button type="button" class="btn btn-secondary" onclick="hideAddUpgradeRecordModal()">Cancel</button>
@if (isNew)

View File

@@ -22,6 +22,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('UpgradeRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('UpgradeRecord')">Export to CSV</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">Print</a></li>
</ul>
</div>
}
@@ -45,7 +47,7 @@
<th scope="col" class="col-2 col-xl-1">Date</th>
<th scope="col" class="col-2">Odometer</th>
<th scope="col" class="col-3 col-xl-4">Description</th>
<th scope="col" class="col-2">Cost</th>
<th scope="col" class="col-2" onclick="toggleSort('upgrade-tab-pane', this)" style="cursor:pointer;">Cost</th>
<th scope="col" class="col-3">Notes</th>
</tr>
</thead>

View File

@@ -5,22 +5,24 @@
var useMPG = config.GetUserConfig(User).UseMPG;
var useUKMPG = config.GetUserConfig(User).UseUKMPG;
var useKwh = Model.VehicleData.IsElectric;
var useHours = Model.VehicleData.UseHours;
string fuelEconomyUnit;
if (useKwh)
{
fuelEconomyUnit = useMPG ? "mi./kWh" : "kWh/100km";
var distanceUnit = useHours ? "h" : (useMPG ? "mi." : "km");
fuelEconomyUnit = useMPG ? $"{distanceUnit}/kWh" : $"kWh/100{distanceUnit}";
}
else if (useMPG && useUKMPG)
{
fuelEconomyUnit = "mpg";
fuelEconomyUnit = useHours ? "h/g" : "mpg";
}
else if (useUKMPG)
{
fuelEconomyUnit = "l/100mi.";
fuelEconomyUnit = useHours ? "l/100h" : "l/100mi.";
}
else
{
fuelEconomyUnit = useMPG ? "mpg" : "l/100km";
fuelEconomyUnit = useHours ? (useMPG ? "h/g" : "l/100h") : (useMPG ? "mpg" : "l/100km");
}
}
@model VehicleHistoryViewModel

View File

@@ -33,6 +33,10 @@
<input class="form-check-input" type="checkbox" role="switch" id="inputIsElectric" checked="@Model.IsElectric">
<label class="form-check-label" for="inputIsElectric">Electric Vehicle</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputUseHours" checked="@Model.UseHours">
<label class="form-check-label" for="inputUseHours">Use Engine Hours</label>
</div>
@if (!string.IsNullOrWhiteSpace(Model.ImageLocation))
{
<label for="inputImage">Replace picture(optional)</label>

View File

@@ -16,6 +16,7 @@
"EnableAutoOdometerInsert": false,
"UseUKMPG": false,
"UseThreeDecimalGasCost": true,
"UseMarkDownOnSavedNotes": false,
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
"DefaultTab": 8,
"UserNameHash": "",

View File

@@ -25,8 +25,16 @@
<h6 class="display-6 text-center">Self-Hosted, Open-Source, Unconventionally-Named Vehicle Maintenance Records and Fuel Mileage Tracker</h6>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#showcase">Showcase</a></div>
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#features">Features</a></div>
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#demo">Demo</a></div>
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="#download">Download</a></div>
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="https://docs.lubelogger.com">Docs</a></div>
<div class="col-12 col-sm-6 col-md-2"><a class="btn btn-dark" href="https://github.com/hargata/lubelog">GitHub Repo</a></div>
</div>
<hr>
<div class="row" id="showcase">
<div class="col-12 d-flex justify-content-center">
<h6 class="display-6 text-center">Showcase</h6>
</div>
@@ -96,7 +104,7 @@
</div>
</div>
<hr>
<div class="row">
<div class="row" id="features">
<div class="col-12 d-flex justify-content-center">
<h6 class="display-6 text-center">Features</h6>
</div>
@@ -109,24 +117,24 @@
<li class="list-group-item">Keeps track of taxes(registration, going fast tax, etc)</li>
<li class="list-group-item">Keeps track of supplies(parts, fluids, etc)</li>
<li class="list-group-item">No limit on how many vehicles you have in your garage</li>
<li class="list-group-item">Import existing records from CSV(supports imports from Fuelly for Fuel Records)</li>
<li class="list-group-item">Import existing records from CSV(supports imports from Fuelly)</li>
<li class="list-group-item">Attach documents for each record(receipts, invoices, etc)</li>
</ul>
</div>
<div class="col-12 col-md-6">
<ul class="list-group">
<li class="list-group-item">Keeps track of your To-Do's(Kanban Planner)</li>
<li class="list-group-item">Set reminders so you never miss another scheduled maintenance</li>
<li class="list-group-item">Set recurring reminders so you never miss another scheduled maintenance</li>
<li class="list-group-item">Dark Mode</li>
<li class="list-group-item">Mobile/Small screen support</li>
<li class="list-group-item">Basic Authentication for security</li>
<li class="list-group-item">API Endpoints</li>
<li class="list-group-item">Consolidated Report Export - Just like CarFax</li>
<li class="list-group-item">Consolidated Vehicle Maintenance Report</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="row" id="demo">
<div class="col-12 d-flex justify-content-center">
<h6 class="display-6 text-center">Try It Out</h6>
</div>
@@ -141,7 +149,7 @@
</p>
</div>
<hr>
<div class="row">
<div class="row" id="download">
<div class="col-12 d-flex justify-content-center">
<h6 class="display-6 text-center">Where to Download</h6>
</div>

View File

@@ -15,6 +15,11 @@ function showEditCollisionRecordModal(collisionRecordId) {
//initiate datepicker
initDatePicker($('#collisionRecordDate'));
$('#collisionRecordModal').modal('show');
$('#collisionRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("collisionRecordNotes");
}
});
}
});
}

View File

@@ -28,4 +28,27 @@ function performLogOut() {
window.location.href = '/Login';
}
})
}
function loadPinnedNotes(vehicleId) {
var hoveredGrid = $(`#gridVehicle_${vehicleId}`);
if (hoveredGrid.attr("data-bs-title") == undefined) {
$.get(`/Vehicle/GetPinnedNotesByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data.length > 0) {
//converted pinned notes to html.
var htmlString = "<ul class='list-group list-group-flush'>";
data.forEach(x => {
htmlString += `<li><b>${x.description}</b> : ${x.noteText}</li>`;
});
htmlString += "</ul>";
hoveredGrid.attr("data-bs-title", htmlString);
new bootstrap.Tooltip(hoveredGrid);
hoveredGrid.tooltip("show");
}
});
} else {
hoveredGrid.tooltip("show");
}
}
function hidePinnedNotes(vehicleId) {
$(`#gridVehicle_${vehicleId}`).tooltip("hide");
}

View File

@@ -1,5 +1,5 @@
function showAddGasRecordModal() {
$.get('/Vehicle/GetAddGasRecordPartialView', function (data) {
$.get(`/Vehicle/GetAddGasRecordPartialView?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
if (data) {
$("#gasRecordModalContent").html(data);
//initiate datepicker
@@ -15,6 +15,11 @@ function showEditGasRecordModal(gasRecordId) {
//initiate datepicker
initDatePicker($('#gasRecordDate'));
$('#gasRecordModal').modal('show');
$('#gasRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("gasRecordNotes");
}
});
}
});
}

View File

@@ -11,6 +11,11 @@ function showEditNoteModal(noteId) {
if (data) {
$("#noteModalContent").html(data);
$('#noteModal').modal('show');
$('#noteModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("noteTextArea");
}
});
}
});
}

View File

@@ -15,6 +15,11 @@ function showEditOdometerRecordModal(odometerRecordId) {
//initiate datepicker
initDatePicker($('#odometerRecordDate'));
$('#odometerRecordModal').modal('show');
$('#odometerRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("odometerRecordNotes");
}
});
}
});
}

View File

@@ -15,6 +15,11 @@ function showEditPlanRecordModal(planRecordId) {
//initiate datepicker
initDatePicker($('#planRecordDate'));
$('#planRecordModal').modal('show');
$('#planRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("planRecordNotes");
}
});
}
});
}

View File

@@ -4,6 +4,11 @@
$("#reminderRecordModalContent").html(data);
initDatePicker($('#reminderDate'), true);
$("#reminderRecordModal").modal("show");
$('#reminderRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("reminderNotes");
}
});
}
});
}

View File

@@ -74,4 +74,60 @@ function refreshCollaborators() {
$.get(`/Vehicle/GetCollaboratorsForVehicle?vehicleId=${vehicleId}`, function (data) {
$("#collaboratorContent").html(data);
});
}
function exportAttachments() {
Swal.fire({
title: 'Export Attachments',
html: `
<div id='attachmentTabs'>
<div class='form-check form-check-inline'>
<input type="checkbox" id="exportServiceRecord" class="form-check-input me-1" value='ServiceRecord'>
<label for="exportServiceRecord" class='form-check-label'>Service Record</label>
</div>
<div class='form-check form-check-inline'>
<input type="checkbox" id="exportRepairRecord" class="form-check-input me-1" value='RepairRecord'>
<label for="exportRepairRecord" class='form-check-label'>Repairs</label>
</div>
<div class='form-check form-check-inline'>
<input type="checkbox" id="exportUpgradeRecord" class="form-check-input me-1" value='UpgradeRecord'>
<label for="exportUpgradeRecord" class='form-check-label'>Upgrades</label>
</div>
<div class='form-check form-check-inline'>
<input type="checkbox" id="exportGasRecord" class="form-check-input me-1" value='GasRecord'>
<label for="exportGasRecord" class='form-check-label'>Fuel</label>
</div>
<div class='form-check form-check-inline'>
<input type="checkbox" id="exportTaxRecord" class="form-check-input me-1" value='TaxRecord'>
<label for="exportTaxRecord" class='form-check-label'>Taxes</label>
</div>
<div class='form-check form-check-inline'>
<input type="checkbox" id="exportOdometerRecord" class="form-check-input me-1" value='OdometerRecord'>
<label for="exportOdometerRecord" class='form-check-label'>Odometer</label>
</div>
</div>
`,
confirmButtonText: 'Export',
showCancelButton: true,
focusConfirm: false,
preConfirm: () => {
var selectedExportTabs = $("#attachmentTabs :checked").map(function () {
return this.value;
});
if (selectedExportTabs.toArray().length == 0) {
Swal.showValidationMessage(`Please make at least one selection`)
}
return { selectedTabs: selectedExportTabs.toArray() }
},
}).then(function (result) {
if (result.isConfirmed) {
var vehicleId = GetVehicleId().vehicleId;
$.post('/Vehicle/GetVehicleAttachments', { vehicleId: vehicleId, exportTabs: result.value.selectedTabs }, function (data) {
if (data.success) {
window.location.href = data.message;
} else {
errorToast(data.message);
}
})
}
});
}

View File

@@ -15,6 +15,11 @@ function showEditServiceRecordModal(serviceRecordId) {
//initiate datepicker
initDatePicker($('#serviceRecordDate'));
$('#serviceRecordModal').modal('show');
$('#serviceRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("serviceRecordNotes");
}
});
}
});
}

View File

@@ -38,6 +38,7 @@ function saveVehicle(isEdit) {
var vehicleModel = $("#inputModel").val();
var vehicleLicensePlate = $("#inputLicensePlate").val();
var vehicleIsElectric = $("#inputIsElectric").is(":checked");
var vehicleUseHours = $("#inputUseHours").is(":checked");
//validate
var hasError = false;
if (vehicleYear.trim() == '' || parseInt(vehicleYear) < 1900) {
@@ -74,7 +75,8 @@ function saveVehicle(isEdit) {
make: vehicleMake,
model: vehicleModel,
licensePlate: vehicleLicensePlate,
isElectric: vehicleIsElectric
isElectric: vehicleIsElectric,
useHours: vehicleUseHours
}, function (data) {
if (data) {
if (!isEdit) {
@@ -108,6 +110,10 @@ function uploadFileAsync(event) {
if (response.trim() != '') {
uploadedFile = response;
}
},
error: function () {
sloader.hide();
errorToast("An error has occurred, please check the file size and try again later.")
}
});
}
@@ -154,4 +160,61 @@ function setDebounce(callBack) {
debounce = setTimeout(function () {
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>';
var sortDescIcon = '<i class="bi bi-sort-numeric-down-alt ms-2"></i>';
sender = $(sender);
//order of sort - asc, desc, reset
if (sender.hasClass('sort-asc')) {
sender.removeClass('sort-asc');
sender.addClass('sort-desc');
sender.html(`${sortColumn}${sortDescIcon}`);
sortTable(tabName, sortColumn, true);
} else if (sender.hasClass('sort-desc')) {
//restore table
sender.removeClass('sort-desc');
sender.html(`${sortColumn}`);
$(`#${tabName} table tbody`).html(storedTableRowState);
} 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)) {
//restore table state.
$(`#${tabName} table tbody`).html(storedTableRowState);
//reset other sorted columns
if ($(".sort-asc").length > 0) {
$(".sort-asc").html($(".sort-asc").html().replace(sortAscIcon, ""));
$(".sort-asc").removeClass("sort-asc");
}
if ($(".sort-desc").length > 0) {
$(".sort-desc").html($(".sort-desc").html().replace(sortDescIcon, ""));
$(".sort-desc").removeClass("sort-desc");
}
}
sender.addClass('sort-asc');
sender.html(`${sortColumn}${sortAscIcon}`);
storedTableRowState = null;
storedTableRowState = $(`#${tabName} table tbody`).html();
sortTable(tabName, sortColumn, false);
}
}
function sortTable(tabName, columnName, desc) {
//get column index.
var columns = $(`#${tabName} table th`).toArray().map(x => x.innerText);
var colIndex = columns.findIndex(x => x == columnName);
//get row data
var rowData = $(`#${tabName} table tbody tr`);
var sortedRow = rowData.toArray().sort((a, b) => {
var currentVal = globalParseFloat(a.children[colIndex].textContent);
var nextVal = globalParseFloat(b.children[colIndex].textContent);
if (desc) {
return nextVal - currentVal;
} else {
return currentVal - nextVal;
}
});
$(`#${tabName} table tbody`).html(sortedRow);
}

View File

@@ -15,6 +15,11 @@ function showEditSupplyRecordModal(supplyRecordId) {
//initiate datepicker
initDatePicker($('#supplyRecordDate'));
$('#supplyRecordModal').modal('show');
$('#supplyRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("supplyRecordNotes");
}
});
}
});
}

View File

@@ -15,6 +15,11 @@ function showEditTaxRecordModal(taxRecordId) {
//initiate datepicker
initDatePicker($('#taxRecordDate'));
$('#taxRecordModal').modal('show');
$('#taxRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("taxRecordNotes");
}
});
}
});
}

View File

@@ -15,6 +15,11 @@ function showEditUpgradeRecordModal(upgradeRecordId) {
//initiate datepicker
initDatePicker($('#upgradeRecordDate'));
$('#upgradeRecordModal').modal('show');
$('#upgradeRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("upgradeRecordNotes");
}
});
}
});
}

View File

@@ -278,6 +278,10 @@ function uploadVehicleFilesAsync(event) {
if (response.length > 0) {
uploadedFiles.push.apply(uploadedFiles, response);
}
},
error: function () {
sloader.hide();
errorToast("An error has occurred, please check the file size and try again later.")
}
});
}
@@ -313,7 +317,11 @@ function getVehicleHaveImportantReminders(vehicleId) {
});
}, 500);
}
function printTab() {
setTimeout(function () {
window.print();
}, 500);
}
function deleteFileFromUploadedFiles(fileLocation, event) {
event.parentElement.parentElement.parentElement.remove();
uploadedFiles = uploadedFiles.filter(x => x.location != fileLocation);
@@ -349,4 +357,82 @@ function saveScrollPosition() {
function restoreScrollPosition() {
$(".vehicleDetailTabContainer").scrollTop(scrollPosition);
scrollPosition = 0;
}
function moveRecord(recordId, source, dest) {
$("#workAroundInput").show();
var friendlySource = "";
var friendlyDest = "";
var hideModalCallBack;
var refreshDataCallBack;
switch (source) {
case "ServiceRecord":
friendlySource = "Service Records";
hideModalCallBack = hideAddServiceRecordModal;
refreshDataCallBack = getVehicleServiceRecords;
break;
case "RepairRecord":
friendlySource = "Repairs";
hideModalCallBack = hideAddCollisionRecordModal;
refreshDataCallBack = getVehicleCollisionRecords;
break;
case "UpgradeRecord":
friendlySource = "Upgrades";
hideModalCallBack = hideAddUpgradeRecordModal;
refreshDataCallBack = getVehicleUpgradeRecords;
break;
}
switch (dest) {
case "ServiceRecord":
friendlyDest = "Service Records";
break;
case "RepairRecord":
friendlyDest = "Repairs";
break;
case "UpgradeRecord":
friendlyDest = "Upgrades";
break;
}
Swal.fire({
title: "Confirm Move?",
text: `Move this record from ${friendlySource} to ${friendlyDest}?`,
showCancelButton: true,
confirmButtonText: "Move",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post('/Vehicle/MoveRecord', {recordId: recordId, source: source, destination: dest }, function (data) {
if (data) {
hideModalCallBack();
successToast("Record Moved");
var vehicleId = GetVehicleId().vehicleId;
refreshDataCallBack(vehicleId);
} else {
errorToast("An error has occurred, please try again later.");
}
});
} else {
$("#workAroundInput").hide();
}
});
}
function toggleMarkDownOverlay(textAreaName) {
var textArea = $(`#${textAreaName}`);
if ($(".markdown-overlay").length > 0) {
$(".markdown-overlay").remove();
return;
}
var text = textArea.val();
if (text == undefined) {
return;
}
if (text.length > 0) {
var formatted = markdown(text);
//var overlay div
var overlayDiv = `<div class='markdown-overlay' style="z-index: 1060; position:absolute; top:${textArea.css('top')}; left:${textArea.css('left')}; width:${textArea.css('width')}; height:${textArea.css('height')}; padding:${textArea.css('padding')}; overflow-y:auto; background-color:var(--bs-modal-bg);">${formatted}</div>`;
textArea.parent().children(`label[for=${textAreaName}]`).append(overlayDiv);
}
}
function showLinks(e) {
var textAreaName = $(e.parentElement).attr("for");
toggleMarkDownOverlay(textAreaName);
}

View File

@@ -0,0 +1,129 @@
/**
* drawdown.js
* (c) Adam Leggett
*/
;function markdown(src) {
var rx_lt = /</g;
var rx_gt = />/g;
var rx_space = /\t|\r|\uf8ff/g;
var rx_escape = /\\([\\\|`*_{}\[\]()#+\-~])/g;
var rx_hr = /^([*\-=_] *){3,}$/gm;
var rx_blockquote = /\n *&gt; *([^]*?)(?=(\n|$){2})/g;
var rx_list = /\n( *)(?:[*\-+]|((\d+)|([a-z])|[A-Z])[.)]) +([^]*?)(?=(\n|$){2})/g;
var rx_listjoin = /<\/(ol|ul)>\n\n<\1>/g;
var rx_highlight = /(^|[^A-Za-z\d\\])(([*_])|(~)|(\^)|(--)|(\+\+)|`)(\2?)([^<]*?)\2\8(?!\2)(?=\W|_|$)/g;
var rx_code = /\n((```|~~~).*\n?([^]*?)\n?\2|(( .*?\n)+))/g;
var rx_link = /((!?)\[(.*?)\]\((.*?)( ".*")?\)|\\([\\`*_{}\[\]()#+\-.!~]))/g;
var rx_table = /\n(( *\|.*?\| *\n)+)/g;
var rx_thead = /^.*\n( *\|( *\:?-+\:?-+\:? *\|)* *\n|)/;
var rx_row = /.*\n/g;
var rx_cell = /\||(.*?[^\\])\|/g;
var rx_heading = /(?=^|>|\n)([>\s]*?)(#{1,6}) (.*?)( #*)? *(?=\n|$)/g;
var rx_para = /(?=^|>|\n)\s*\n+([^<]+?)\n+\s*(?=\n|<|$)/g;
var rx_stash = /-\d+\uf8ff/g;
function replace(rex, fn) {
src = src.replace(rex, fn);
}
function element(tag, content) {
return '<' + tag + '>' + content + '</' + tag + '>';
}
function blockquote(src) {
return src.replace(rx_blockquote, function(all, content) {
return element('blockquote', blockquote(highlight(content.replace(/^ *&gt; */gm, ''))));
});
}
function list(src) {
return src.replace(rx_list, function(all, ind, ol, num, low, content) {
var entry = element('li', highlight(content.split(
RegExp('\n ?' + ind + '(?:(?:\\d+|[a-zA-Z])[.)]|[*\\-+]) +', 'g')).map(list).join('</li><li>')));
return '\n' + (ol
? '<ol start="' + (num
? ol + '">'
: parseInt(ol,36) - 9 + '" style="list-style-type:' + (low ? 'low' : 'upp') + 'er-alpha">') + entry + '</ol>'
: element('ul', entry));
});
}
function highlight(src) {
return src.replace(rx_highlight, function(all, _, p1, emp, sub, sup, small, big, p2, content) {
return _ + element(
emp ? (p2 ? 'strong' : 'em')
: sub ? (p2 ? 's' : 'sub')
: sup ? 'sup'
: small ? 'small'
: big ? 'big'
: 'code',
highlight(content));
});
}
function unesc(str) {
return str.replace(rx_escape, '$1');
}
var stash = [];
var si = 0;
src = '\n' + src + '\n';
replace(rx_lt, '&lt;');
replace(rx_gt, '&gt;');
replace(rx_space, ' ');
// blockquote
src = blockquote(src);
// horizontal rule
replace(rx_hr, '<hr/>');
// list
src = list(src);
replace(rx_listjoin, '');
// code
replace(rx_code, function(all, p1, p2, p3, p4) {
stash[--si] = element('pre', element('code', p3||p4.replace(/^ /gm, '')));
return si + '\uf8ff';
});
// link or image
replace(rx_link, function(all, p1, p2, p3, p4, p5, p6) {
stash[--si] = p4
? p2
? '<img src="' + p4 + '" alt="' + p3 + '"/>'
: '<a href="' + p4 + '">' + unesc(highlight(p3)) + '</a>'
: p6;
return si + '\uf8ff';
});
// table
replace(rx_table, function(all, table) {
var sep = table.match(rx_thead)[1];
return '\n' + element('table',
table.replace(rx_row, function(row, ri) {
return row == sep ? '' : element('tr', row.replace(rx_cell, function(all, cell, ci) {
return ci ? element(sep && !ri ? 'th' : 'td', unesc(highlight(cell || ''))) : ''
}))
})
)
});
// heading
replace(rx_heading, function(all, _, p1, p2) { return _ + element('h' + p1.length, unesc(highlight(p2))) });
// paragraph
replace(rx_para, function(all, content) { return element('p', unesc(highlight(content))) });
// stash
replace(rx_stash, function(all) { return stash[parseInt(all)] });
return src.trim();
};