Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a007530a6 | ||
|
|
04ce448b23 | ||
|
|
ccd446f299 | ||
|
|
c49c8a5301 | ||
|
|
55cc2819d0 | ||
|
|
f8de7de0d6 | ||
|
|
2ea1bc2c20 | ||
|
|
90d095ea51 | ||
|
|
e1d12d0918 | ||
|
|
d9d0957040 | ||
|
|
9d73db3c51 | ||
|
|
c0f0786fd4 | ||
|
|
357eff116f | ||
|
|
32a047c522 | ||
|
|
8a237bb7ec | ||
|
|
f5b9072cc6 | ||
|
|
4e3eaa53ff | ||
|
|
4779d3f161 | ||
|
|
b0173fae94 | ||
|
|
c6ee8830a3 | ||
|
|
c2eeab5025 | ||
|
|
43fd40347f | ||
|
|
53139f9bb2 | ||
|
|
0b6033cc00 | ||
|
|
6f0115e5c5 | ||
|
|
2403adf537 | ||
|
|
f00ab897b5 | ||
|
|
093631bf6f | ||
|
|
5c2835ab76 | ||
|
|
03029981fd | ||
|
|
69b1838038 | ||
|
|
1c2f83026c | ||
|
|
d36f2c59e3 | ||
|
|
53ebec3f03 | ||
|
|
f2cbbaeb12 | ||
|
|
31c1202649 |
3
.env
3
.env
@@ -5,4 +5,5 @@ MailConfig__EmailFrom=""
|
||||
MailConfig__UseSSL="false"
|
||||
MailConfig__Port=587
|
||||
MailConfig__Username=""
|
||||
MailConfig__Password=""
|
||||
MailConfig__Password=""
|
||||
LOGGING__LOGLEVEL__DEFAULT=Error
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]),
|
||||
|
||||
@@ -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")}";
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
14
Models/GenericRecord.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"EnableAutoOdometerInsert": false,
|
||||
"UseUKMPG": false,
|
||||
"UseThreeDecimalGasCost": true,
|
||||
"UseMarkDownOnSavedNotes": false,
|
||||
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
|
||||
"DefaultTab": 8,
|
||||
"UserNameHash": "",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
129
wwwroot/lib/drawdown/drawdown.js
Normal file
129
wwwroot/lib/drawdown/drawdown.js
Normal 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 *> *([^]*?)(?=(\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(/^ *> */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, '<');
|
||||
replace(rx_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();
|
||||
};
|
||||
Reference in New Issue
Block a user