Compare commits
186 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 | ||
|
|
331499461a | ||
|
|
8c6dd5e343 | ||
|
|
01b1e8228d | ||
|
|
43979d6115 | ||
|
|
6a9860e202 | ||
|
|
39338e8028 | ||
|
|
d1d5351a01 | ||
|
|
c9385c7fdd | ||
|
|
afaae89af6 | ||
|
|
b9d799cd49 | ||
|
|
e47c541e08 | ||
|
|
51ff01d2cd | ||
|
|
618399cb09 | ||
|
|
b837a2e528 | ||
|
|
a1e8b8f9cc | ||
|
|
787c5da72a | ||
|
|
260703be8e | ||
|
|
053801b046 | ||
|
|
db9b1970c5 | ||
|
|
b153ef5ea5 | ||
|
|
b54809f399 | ||
|
|
f7f47c54ff | ||
|
|
92564ae527 | ||
|
|
52ada8574d | ||
|
|
013fb67943 | ||
|
|
d86298f502 | ||
|
|
5891b78be0 | ||
|
|
a9e3e44f2c | ||
|
|
0d1c7234e8 | ||
|
|
e3abe5f209 | ||
|
|
b453bfce5b | ||
|
|
147a1b03a7 | ||
|
|
3017db5f86 | ||
|
|
399d0d8058 | ||
|
|
b8c0d4ef67 | ||
|
|
918d086705 | ||
|
|
ac05acf96b | ||
|
|
e7449806c0 | ||
|
|
baab3213b5 | ||
|
|
d68e9e9589 | ||
|
|
be81f9727a | ||
|
|
04b7e7fd38 | ||
|
|
e6b50fafd2 | ||
|
|
d4896a7607 | ||
|
|
0b203709fa | ||
|
|
df5faba146 | ||
|
|
d8f8b63488 | ||
|
|
c100fc76ed | ||
|
|
c553d87600 | ||
|
|
b542bd54fb | ||
|
|
4ec11a47a1 | ||
|
|
175ce2be48 | ||
|
|
aad1655f2e | ||
|
|
85eb0b70e6 | ||
|
|
f54e12886a | ||
|
|
80ebe4c292 | ||
|
|
92b3bc3aea | ||
|
|
2cfb82c235 | ||
|
|
dd693323d7 | ||
|
|
5c8f03003e | ||
|
|
023fac2ea9 | ||
|
|
9086c26b5e | ||
|
|
0af8e99e61 | ||
|
|
4ca45dd32b | ||
|
|
127753ee86 | ||
|
|
30a9411cdd | ||
|
|
e801a4a77c | ||
|
|
d8c49995ce | ||
|
|
0c93663e51 | ||
|
|
605ac07594 | ||
|
|
9a7f2233a0 | ||
|
|
1339c427c4 | ||
|
|
8d79804872 | ||
|
|
3598bb6adb | ||
|
|
50b18a1a71 | ||
|
|
0ab585d4cc | ||
|
|
d32b9b879d | ||
|
|
594ad38454 | ||
|
|
ae8a885b4d | ||
|
|
975bbadaae | ||
|
|
42711bc92a | ||
|
|
f4538ffabd | ||
|
|
d60c3d3ea6 | ||
|
|
3cc32e48f4 | ||
|
|
0d4b7d8ee1 | ||
|
|
d8249c7163 | ||
|
|
60edb65b55 | ||
|
|
bc2bb3636b | ||
|
|
58c49c1240 | ||
|
|
2eb9a58aa6 | ||
|
|
b05ded67a9 | ||
|
|
76c9473704 | ||
|
|
3909223d2f | ||
|
|
ab720272da | ||
|
|
b702a663f4 | ||
|
|
1d8baaf423 | ||
|
|
0869c99090 | ||
|
|
48b2bab5d6 | ||
|
|
0bfa036603 | ||
|
|
a02bcf94d7 | ||
|
|
9e0b45deac | ||
|
|
1b04faad9c | ||
|
|
17f5cf6a1d | ||
|
|
bf7cffdf8f | ||
|
|
a3ba527765 | ||
|
|
a085b2d87d | ||
|
|
06f1ce5884 | ||
|
|
c77f9bce51 | ||
|
|
cf11c203ce | ||
|
|
4550d33b92 | ||
|
|
503127eb03 | ||
|
|
dbb139dfad | ||
|
|
e1a5a871ae | ||
|
|
0714ec6432 | ||
|
|
36339a04e1 | ||
|
|
8af9868d2f | ||
|
|
99d5372f25 | ||
|
|
bd6defe205 | ||
|
|
f5276031c0 | ||
|
|
50ebdf547a | ||
|
|
ba248758cb | ||
|
|
4bde8181b7 | ||
|
|
d0d733b7d2 | ||
|
|
e377f6c8d0 | ||
|
|
2fafe1101c | ||
|
|
56a315524a | ||
|
|
244a164891 | ||
|
|
2ec0c1d465 | ||
|
|
239ca73a64 | ||
|
|
b03460d6bb | ||
|
|
d2686949c5 | ||
|
|
971242b015 | ||
|
|
bd3b821226 | ||
|
|
6ffa856795 | ||
|
|
6895d2060d | ||
|
|
d413a06b87 | ||
|
|
263000f0ae | ||
|
|
578f6ab62a | ||
|
|
469b625989 | ||
|
|
95a630b600 | ||
|
|
5f576a2792 | ||
|
|
f5af92da93 | ||
|
|
4ccfcd6be7 | ||
|
|
917d093bcf | ||
|
|
c916771815 | ||
|
|
d4517ac986 | ||
|
|
040cc6adf9 | ||
|
|
ec740089fe | ||
|
|
e29e14bc3e | ||
|
|
592baa4c6e |
5
.env
5
.env
@@ -2,7 +2,8 @@ LC_ALL=en_US.UTF-8
|
||||
LANG=en_US.UTF-8
|
||||
MailConfig__EmailServer=""
|
||||
MailConfig__EmailFrom=""
|
||||
MailConfig__UseSSL=""
|
||||
MailConfig__UseSSL="false"
|
||||
MailConfig__Port=587
|
||||
MailConfig__Username=""
|
||||
MailConfig__Password=""
|
||||
MailConfig__Password=""
|
||||
LOGGING__LOGLEVEL__DEFAULT=Error
|
||||
30
.github/workflows/dockerhub-docker-image.yml
vendored
Normal file
30
.github/workflows/dockerhub-docker-image.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Docker Image To Docker Hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: "${{ secrets.DH_USER }}"
|
||||
password: "${{ secrets.DH_PASS }}"
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
hargata/lubelogger:latest
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Docker Image CI
|
||||
name: Docker Image To GHCR
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/hargata/lubelogger:latest
|
||||
ghcr.io/hargata/lubelogger:latest
|
||||
@@ -1,4 +1,5 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.External.Implementations;
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Filter;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Logic;
|
||||
@@ -20,9 +21,14 @@ namespace CarCareTracker.Controllers
|
||||
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
|
||||
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
||||
private readonly IUserAccessDataAccess _userAccessDataAccess;
|
||||
private readonly IUserRecordDataAccess _userRecordDataAccess;
|
||||
private readonly IReminderHelper _reminderHelper;
|
||||
private readonly IGasHelper _gasHelper;
|
||||
private readonly IUserLogic _userLogic;
|
||||
private readonly IFileHelper _fileHelper;
|
||||
private readonly IMailHelper _mailHelper;
|
||||
public APIController(IVehicleDataAccess dataAccess,
|
||||
IGasHelper gasHelper,
|
||||
IReminderHelper reminderHelper,
|
||||
@@ -33,6 +39,11 @@ namespace CarCareTracker.Controllers
|
||||
ITaxRecordDataAccess taxRecordDataAccess,
|
||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||
IUpgradeRecordDataAccess upgradeRecordDataAccess,
|
||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||
IUserAccessDataAccess userAccessDataAccess,
|
||||
IUserRecordDataAccess userRecordDataAccess,
|
||||
IMailHelper mailHelper,
|
||||
IFileHelper fileHelper,
|
||||
IUserLogic userLogic)
|
||||
{
|
||||
_dataAccess = dataAccess;
|
||||
@@ -43,9 +54,14 @@ namespace CarCareTracker.Controllers
|
||||
_taxRecordDataAccess = taxRecordDataAccess;
|
||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||
_userAccessDataAccess = userAccessDataAccess;
|
||||
_userRecordDataAccess = userRecordDataAccess;
|
||||
_mailHelper = mailHelper;
|
||||
_gasHelper = gasHelper;
|
||||
_reminderHelper = reminderHelper;
|
||||
_userLogic = userLogic;
|
||||
_fileHelper = fileHelper;
|
||||
}
|
||||
public IActionResult Index()
|
||||
{
|
||||
@@ -76,6 +92,53 @@ namespace CarCareTracker.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/servicerecords/add")]
|
||||
public IActionResult AddServiceRecord(int vehicleId, ServiceRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(input.Date) ||
|
||||
string.IsNullOrWhiteSpace(input.Description) ||
|
||||
string.IsNullOrWhiteSpace(input.Odometer) ||
|
||||
string.IsNullOrWhiteSpace(input.Cost))
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
try
|
||||
{
|
||||
var serviceRecord = new ServiceRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
};
|
||||
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
|
||||
response.Success = true;
|
||||
response.Message = "Service Record Added";
|
||||
return Json(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = ex.Message;
|
||||
Response.StatusCode = 500;
|
||||
return Json(response);
|
||||
}
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/repairrecords")]
|
||||
public IActionResult RepairRecords(int vehicleId)
|
||||
@@ -85,6 +148,53 @@ namespace CarCareTracker.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/repairrecords/add")]
|
||||
public IActionResult AddRepairRecord(int vehicleId, ServiceRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(input.Date) ||
|
||||
string.IsNullOrWhiteSpace(input.Description) ||
|
||||
string.IsNullOrWhiteSpace(input.Odometer) ||
|
||||
string.IsNullOrWhiteSpace(input.Cost))
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
try
|
||||
{
|
||||
var repairRecord = new CollisionRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
};
|
||||
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
|
||||
response.Success = true;
|
||||
response.Message = "Repair Record Added";
|
||||
return Json(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = ex.Message;
|
||||
Response.StatusCode = 500;
|
||||
return Json(response);
|
||||
}
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/upgraderecords")]
|
||||
public IActionResult UpgradeRecords(int vehicleId)
|
||||
@@ -94,6 +204,53 @@ namespace CarCareTracker.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/upgraderecords/add")]
|
||||
public IActionResult AddUpgradeRecord(int vehicleId, ServiceRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(input.Date) ||
|
||||
string.IsNullOrWhiteSpace(input.Description) ||
|
||||
string.IsNullOrWhiteSpace(input.Odometer) ||
|
||||
string.IsNullOrWhiteSpace(input.Cost))
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
try
|
||||
{
|
||||
var upgradeRecord = new UpgradeRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
};
|
||||
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
|
||||
response.Success = true;
|
||||
response.Message = "Upgrade Record Added";
|
||||
return Json(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = ex.Message;
|
||||
Response.StatusCode = 500;
|
||||
return Json(response);
|
||||
}
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/taxrecords")]
|
||||
public IActionResult TaxRecords(int vehicleId)
|
||||
@@ -102,15 +259,173 @@ namespace CarCareTracker.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/taxrecords/add")]
|
||||
public IActionResult AddTaxRecord(int vehicleId, TaxRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(input.Date) ||
|
||||
string.IsNullOrWhiteSpace(input.Description) ||
|
||||
string.IsNullOrWhiteSpace(input.Cost))
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Input object invalid, Date, Description, and Cost cannot be empty.";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
try
|
||||
{
|
||||
var taxRecord = new TaxRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Description = input.Description,
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
};
|
||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
|
||||
response.Success = true;
|
||||
response.Message = "Tax Record Added";
|
||||
return Json(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = ex.Message;
|
||||
Response.StatusCode = 500;
|
||||
return Json(response);
|
||||
}
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/odometerrecords")]
|
||||
public IActionResult OdometerRecords(int vehicleId)
|
||||
{
|
||||
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), Odometer = x.Mileage.ToString(), Notes = x.Notes });
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/odometerrecords/add")]
|
||||
public IActionResult AddOdometerRecord(int vehicleId, OdometerRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(input.Date) ||
|
||||
string.IsNullOrWhiteSpace(input.Odometer))
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Input object invalid, Date and Odometer cannot be empty.";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
try
|
||||
{
|
||||
var odometerRecord = new OdometerRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Mileage = int.Parse(input.Odometer)
|
||||
};
|
||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
||||
response.Success = true;
|
||||
response.Message = "Odometer Record Added";
|
||||
return Json(response);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = ex.Message;
|
||||
Response.StatusCode = 500;
|
||||
return Json(response);
|
||||
}
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/gasrecords")]
|
||||
public IActionResult GasRecords(int vehicleId, bool useMPG, bool useUKMPG)
|
||||
{
|
||||
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||
var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG).Select(x => new GasRecordExportModel { Date = x.Date, Odometer = x.Mileage.ToString(), Cost = x.Cost.ToString(), FuelConsumed = x.Gallons.ToString(), FuelEconomy = x.MilesPerGallon.ToString()});
|
||||
var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG)
|
||||
.Select(x => new GasRecordExportModel {
|
||||
Date = x.Date,
|
||||
Odometer = x.Mileage.ToString(),
|
||||
Cost = x.Cost.ToString(),
|
||||
FuelConsumed = x.Gallons.ToString(),
|
||||
FuelEconomy = x.MilesPerGallon.ToString(),
|
||||
IsFillToFull = x.IsFillToFull.ToString(),
|
||||
MissedFuelUp = x.MissedFuelUp.ToString(),
|
||||
Notes = x.Notes
|
||||
});
|
||||
return Json(result);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpPost]
|
||||
[Route("/api/vehicle/gasrecords/add")]
|
||||
public IActionResult AddGasRecord(int vehicleId, GasRecordExportModel input)
|
||||
{
|
||||
var response = new OperationResponse();
|
||||
if (vehicleId == default)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Must provide a valid vehicle id";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(input.Date) ||
|
||||
string.IsNullOrWhiteSpace(input.Odometer) ||
|
||||
string.IsNullOrWhiteSpace(input.FuelConsumed) ||
|
||||
string.IsNullOrWhiteSpace(input.Cost) ||
|
||||
string.IsNullOrWhiteSpace(input.IsFillToFull) ||
|
||||
string.IsNullOrWhiteSpace(input.MissedFuelUp)
|
||||
)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = "Input object invalid, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty.";
|
||||
Response.StatusCode = 400;
|
||||
return Json(response);
|
||||
}
|
||||
try
|
||||
{
|
||||
var gasRecord = new GasRecord()
|
||||
{
|
||||
VehicleId = vehicleId,
|
||||
Date = DateTime.Parse(input.Date),
|
||||
Mileage = int.Parse(input.Odometer),
|
||||
Gallons = decimal.Parse(input.FuelConsumed),
|
||||
IsFillToFull = bool.Parse(input.IsFillToFull),
|
||||
MissedFuelUp = bool.Parse(input.MissedFuelUp),
|
||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||
Cost = decimal.Parse(input.Cost)
|
||||
};
|
||||
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
|
||||
response.Success = true;
|
||||
response.Message = "Gas Record Added";
|
||||
return Json(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Message = ex.Message;
|
||||
Response.StatusCode = 500;
|
||||
return Json(response);
|
||||
}
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/reminders")]
|
||||
public IActionResult Reminders(int vehicleId)
|
||||
@@ -120,6 +435,67 @@ namespace CarCareTracker.Controllers
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes});
|
||||
return Json(results);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
[Route("/api/vehicle/reminders/send")]
|
||||
public IActionResult SendReminders(List<ReminderUrgency> urgencies)
|
||||
{
|
||||
var vehicles = _dataAccess.GetVehicles();
|
||||
List<OperationResponse> operationResponses = new List<OperationResponse>();
|
||||
foreach(Vehicle vehicle in vehicles)
|
||||
{
|
||||
var vehicleId = vehicle.Id;
|
||||
//get reminders
|
||||
var currentMileage = GetMaxMileage(vehicleId);
|
||||
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).OrderByDescending(x => x.Urgency).ToList();
|
||||
results.RemoveAll(x => !urgencies.Contains(x.Urgency));
|
||||
if (!results.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//get list of recipients.
|
||||
var userIds = _userAccessDataAccess.GetUserAccessByVehicleId(vehicleId).Select(x => x.Id.UserId);
|
||||
List<string> emailRecipients = new List<string>();
|
||||
foreach (int userId in userIds)
|
||||
{
|
||||
var userData = _userRecordDataAccess.GetUserRecordById(userId);
|
||||
emailRecipients.Add(userData.EmailAddress);
|
||||
};
|
||||
if (!emailRecipients.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var result = _mailHelper.NotifyUserForReminders(vehicle, emailRecipients, results);
|
||||
operationResponses.Add(result);
|
||||
}
|
||||
if (operationResponses.All(x => x.Success))
|
||||
{
|
||||
return Json(new OperationResponse { Success = true, Message = "Emails sent" });
|
||||
} else if (operationResponses.All(x => !x.Success))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "All emails failed, check SMTP settings" });
|
||||
} else
|
||||
{
|
||||
return Json(new OperationResponse { Success = true, Message = "Some emails sent, some failed, check recipient settings" });
|
||||
}
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
[Route("/api/makebackup")]
|
||||
public IActionResult MakeBackup()
|
||||
{
|
||||
var result = _fileHelper.MakeBackup();
|
||||
return Json(result);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
[Route("/api/demo/restore")]
|
||||
public IActionResult RestoreDemo()
|
||||
{
|
||||
var result = _fileHelper.RestoreBackup("/defaults/demo_default.zip", true);
|
||||
return Json(result);
|
||||
}
|
||||
private int GetMaxMileage(int vehicleId)
|
||||
{
|
||||
var numbersArray = new List<int>();
|
||||
@@ -143,6 +519,11 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
numbersArray.Add(upgradeRecords.Max(x => x.Mileage));
|
||||
}
|
||||
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
if (odometerRecords.Any())
|
||||
{
|
||||
numbersArray.Add(odometerRecords.Max(x => x.Mileage));
|
||||
}
|
||||
return numbersArray.Any() ? numbersArray.Max() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
public IActionResult Unauthorized()
|
||||
{
|
||||
if (!User.IsInRole("CookieAuth"))
|
||||
{
|
||||
Response.StatusCode = 403;
|
||||
return new EmptyResult();
|
||||
}
|
||||
return View("401");
|
||||
}
|
||||
}
|
||||
@@ -46,12 +46,25 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult DeleteFiles(string fileLocation)
|
||||
public IActionResult DeleteFiles(string fileLocation)
|
||||
{
|
||||
var result = _fileHelper.DeleteFile(fileLocation);
|
||||
return Json(result);
|
||||
}
|
||||
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
public IActionResult MakeBackup()
|
||||
{
|
||||
var result = _fileHelper.MakeBackup();
|
||||
return Json(result);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult RestoreBackup(string fileName)
|
||||
{
|
||||
var result = _fileHelper.RestoreBackup(fileName);
|
||||
return Json(result);
|
||||
}
|
||||
private string UploadFile(IFormFile fileToUpload)
|
||||
{
|
||||
string uploadDirectory = "temp/";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,13 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
|
||||
WORKDIR /App
|
||||
|
||||
COPY . ./
|
||||
RUN dotnet restore
|
||||
RUN dotnet publish -c Release -o out
|
||||
ARG TARGETARCH
|
||||
RUN dotnet restore -a $TARGETARCH
|
||||
RUN dotnet publish -a $TARGETARCH -c Release -o out
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
WORKDIR /App
|
||||
COPY --from=build-env /App/out .
|
||||
EXPOSE 8080
|
||||
CMD ["./CarCareTracker"]
|
||||
CMD ["./CarCareTracker"]
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
RepairRecord = 1,
|
||||
GasRecord = 2,
|
||||
TaxRecord = 3,
|
||||
UpgradeRecord = 4
|
||||
UpgradeRecord = 4,
|
||||
ReminderRecord = 5,
|
||||
NoteRecord = 6,
|
||||
SupplyRecord = 7,
|
||||
Dashboard = 8,
|
||||
PlanRecord = 9,
|
||||
OdometerRecord = 10
|
||||
}
|
||||
}
|
||||
|
||||
9
Enum/PlanPriority.cs
Normal file
9
Enum/PlanPriority.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum PlanPriority
|
||||
{
|
||||
Critical = 0,
|
||||
Normal = 1,
|
||||
Low = 2
|
||||
}
|
||||
}
|
||||
10
Enum/PlanProgress.cs
Normal file
10
Enum/PlanProgress.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum PlanProgress
|
||||
{
|
||||
Backlog = 0,
|
||||
InProgress = 1,
|
||||
Testing = 2,
|
||||
Done = 3
|
||||
}
|
||||
}
|
||||
23
Enum/ReminderMileageInterval.cs
Normal file
23
Enum/ReminderMileageInterval.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum ReminderMileageInterval
|
||||
{
|
||||
FiftyMiles = 50,
|
||||
OneHundredMiles = 100,
|
||||
FiveHundredMiles = 500,
|
||||
OneThousandMiles = 1000,
|
||||
ThreeThousandMiles = 3000,
|
||||
FourThousandMiles = 4000,
|
||||
FiveThousandMiles = 5000,
|
||||
SevenThousandFiveHundredMiles = 7500,
|
||||
TenThousandMiles = 10000,
|
||||
FifteenThousandMiles = 15000,
|
||||
TwentyThousandMiles = 20000,
|
||||
ThirtyThousandMiles = 30000,
|
||||
FortyThousandMiles = 40000,
|
||||
FiftyThousandMiles = 50000,
|
||||
SixtyThousandMiles = 60000,
|
||||
OneHundredThousandMiles = 100000,
|
||||
OneHundredFiftyThousandMiles = 150000
|
||||
}
|
||||
}
|
||||
13
Enum/ReminderMonthInterval.cs
Normal file
13
Enum/ReminderMonthInterval.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum ReminderMonthInterval
|
||||
{
|
||||
OneMonth = 1,
|
||||
ThreeMonths = 3,
|
||||
SixMonths = 6,
|
||||
OneYear = 12,
|
||||
TwoYears = 24,
|
||||
ThreeYears = 36,
|
||||
FiveYears = 60
|
||||
}
|
||||
}
|
||||
57
External/Implementations/OdometerRecordDataAccess.cs
vendored
Normal file
57
External/Implementations/OdometerRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class OdometerRecordDataAccess : IOdometerRecordDataAccess
|
||||
{
|
||||
private static string dbName = StaticHelper.DbName;
|
||||
private static string tableName = "odometerrecords";
|
||||
public List<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<OdometerRecord>(tableName);
|
||||
var odometerRecords = table.Find(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
|
||||
return odometerRecords.ToList() ?? new List<OdometerRecord>();
|
||||
};
|
||||
}
|
||||
public OdometerRecord GetOdometerRecordById(int odometerRecordId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<OdometerRecord>(tableName);
|
||||
return table.FindById(odometerRecordId);
|
||||
};
|
||||
}
|
||||
public bool DeleteOdometerRecordById(int odometerRecordId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<OdometerRecord>(tableName);
|
||||
table.Delete(odometerRecordId);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<OdometerRecord>(tableName);
|
||||
table.Upsert(odometerRecord);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<OdometerRecord>(tableName);
|
||||
var odometerRecords = table.DeleteMany(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
57
External/Implementations/PlanRecordDataAccess.cs
vendored
Normal file
57
External/Implementations/PlanRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class PlanRecordDataAccess : IPlanRecordDataAccess
|
||||
{
|
||||
private static string dbName = StaticHelper.DbName;
|
||||
private static string tableName = "planrecords";
|
||||
public List<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecord>(tableName);
|
||||
var planRecords = table.Find(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
|
||||
return planRecords.ToList() ?? new List<PlanRecord>();
|
||||
};
|
||||
}
|
||||
public PlanRecord GetPlanRecordById(int planRecordId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecord>(tableName);
|
||||
return table.FindById(planRecordId);
|
||||
};
|
||||
}
|
||||
public bool DeletePlanRecordById(int planRecordId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecord>(tableName);
|
||||
table.Delete(planRecordId);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
public bool SavePlanRecordToVehicle(PlanRecord planRecord)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecord>(tableName);
|
||||
table.Upsert(planRecord);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<PlanRecord>(tableName);
|
||||
var planRecords = table.DeleteMany(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
57
External/Implementations/SupplyRecordDataAccess.cs
vendored
Normal file
57
External/Implementations/SupplyRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
public class SupplyRecordDataAccess : ISupplyRecordDataAccess
|
||||
{
|
||||
private static string dbName = StaticHelper.DbName;
|
||||
private static string tableName = "supplyrecords";
|
||||
public List<SupplyRecord> GetSupplyRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<SupplyRecord>(tableName);
|
||||
var supplyRecords = table.Find(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
|
||||
return supplyRecords.ToList() ?? new List<SupplyRecord>();
|
||||
};
|
||||
}
|
||||
public SupplyRecord GetSupplyRecordById(int supplyRecordId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<SupplyRecord>(tableName);
|
||||
return table.FindById(supplyRecordId);
|
||||
};
|
||||
}
|
||||
public bool DeleteSupplyRecordById(int supplyRecordId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<SupplyRecord>(tableName);
|
||||
table.Delete(supplyRecordId);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
public bool SaveSupplyRecordToVehicle(SupplyRecord supplyRecord)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<SupplyRecord>(tableName);
|
||||
table.Upsert(supplyRecord);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
public bool DeleteAllSupplyRecordsByVehicleId(int vehicleId)
|
||||
{
|
||||
using (var db = new LiteDatabase(dbName))
|
||||
{
|
||||
var table = db.GetCollection<SupplyRecord>(tableName);
|
||||
var supplyRecords = table.DeleteMany(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
13
External/Interfaces/IOdometerRecordDataAccess.cs
vendored
Normal file
13
External/Interfaces/IOdometerRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
using CarCareTracker.Models;
|
||||
|
||||
namespace CarCareTracker.External.Interfaces
|
||||
{
|
||||
public interface IOdometerRecordDataAccess
|
||||
{
|
||||
public List<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId);
|
||||
public OdometerRecord GetOdometerRecordById(int odometerRecordId);
|
||||
public bool DeleteOdometerRecordById(int odometerRecordId);
|
||||
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord);
|
||||
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId);
|
||||
}
|
||||
}
|
||||
13
External/Interfaces/IPlanRecordDataAccess.cs
vendored
Normal file
13
External/Interfaces/IPlanRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
using CarCareTracker.Models;
|
||||
|
||||
namespace CarCareTracker.External.Interfaces
|
||||
{
|
||||
public interface IPlanRecordDataAccess
|
||||
{
|
||||
public List<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId);
|
||||
public PlanRecord GetPlanRecordById(int planRecordId);
|
||||
public bool DeletePlanRecordById(int planRecordId);
|
||||
public bool SavePlanRecordToVehicle(PlanRecord planRecord);
|
||||
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId);
|
||||
}
|
||||
}
|
||||
13
External/Interfaces/ISupplyRecordDataAccess.cs
vendored
Normal file
13
External/Interfaces/ISupplyRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
using CarCareTracker.Models;
|
||||
|
||||
namespace CarCareTracker.External.Interfaces
|
||||
{
|
||||
public interface ISupplyRecordDataAccess
|
||||
{
|
||||
public List<SupplyRecord> GetSupplyRecordsByVehicleId(int vehicleId);
|
||||
public SupplyRecord GetSupplyRecordById(int supplyRecordId);
|
||||
public bool DeleteSupplyRecordById(int supplyRecordId);
|
||||
public bool SaveSupplyRecordToVehicle(SupplyRecord supplyRecord);
|
||||
public bool DeleteAllSupplyRecordsByVehicleId(int vehicleId);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,13 @@ namespace CarCareTracker.Helper
|
||||
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
|
||||
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
|
||||
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
|
||||
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)])
|
||||
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)]),
|
||||
VisibleTabs = _config.GetSection("VisibleTabs").Get<List<ImportMode>>(),
|
||||
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)])
|
||||
};
|
||||
int userId = 0;
|
||||
if (user != null)
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
namespace CarCareTracker.Helper
|
||||
using CarCareTracker.Models;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
public interface IFileHelper
|
||||
{
|
||||
string GetFullFilePath(string currentFilePath, bool mustExist = true);
|
||||
string MoveFileFromTemp(string currentFilePath, string newFolder);
|
||||
bool DeleteFile(string currentFilePath);
|
||||
string MakeBackup();
|
||||
bool RestoreBackup(string fileName, bool clearExisting = false);
|
||||
string MakeAttachmentsExport(List<GenericReportModel> exportData);
|
||||
}
|
||||
public class FileHelper: IFileHelper
|
||||
public class FileHelper : IFileHelper
|
||||
{
|
||||
private readonly IWebHostEnvironment _webEnv;
|
||||
public FileHelper(IWebHostEnvironment webEnv)
|
||||
private readonly ILogger<IFileHelper> _logger;
|
||||
public FileHelper(IWebHostEnvironment webEnv, ILogger<IFileHelper> logger)
|
||||
{
|
||||
_webEnv = webEnv;
|
||||
_logger = logger;
|
||||
}
|
||||
public string GetFullFilePath(string currentFilePath, bool mustExist = true)
|
||||
{
|
||||
@@ -23,7 +31,8 @@
|
||||
if (File.Exists(oldFilePath))
|
||||
{
|
||||
return oldFilePath;
|
||||
} else if (!mustExist)
|
||||
}
|
||||
else if (!mustExist)
|
||||
{
|
||||
return oldFilePath;
|
||||
}
|
||||
@@ -31,6 +40,163 @@
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
public bool RestoreBackup(string fileName, bool clearExisting = false)
|
||||
{
|
||||
var fullFilePath = GetFullFilePath(fileName);
|
||||
if (string.IsNullOrWhiteSpace(fullFilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{Guid.NewGuid()}");
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
//extract zip file
|
||||
ZipFile.ExtractToDirectory(fullFilePath, tempPath);
|
||||
//copy over images and documents.
|
||||
var imagePath = Path.Combine(tempPath, "images");
|
||||
var documentPath = Path.Combine(tempPath, "documents");
|
||||
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
||||
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
||||
if (Directory.Exists(imagePath))
|
||||
{
|
||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "images");
|
||||
if (!Directory.Exists(existingPath))
|
||||
{
|
||||
Directory.CreateDirectory(existingPath);
|
||||
}
|
||||
else if (clearExisting)
|
||||
{
|
||||
var filesToDelete = Directory.GetFiles(existingPath);
|
||||
foreach (string file in filesToDelete)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
//copy each files from temp folder to newPath
|
||||
var filesToUpload = Directory.GetFiles(imagePath);
|
||||
foreach (string file in filesToUpload)
|
||||
{
|
||||
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
|
||||
}
|
||||
}
|
||||
if (Directory.Exists(documentPath))
|
||||
{
|
||||
var existingPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||
if (!Directory.Exists(existingPath))
|
||||
{
|
||||
Directory.CreateDirectory(existingPath);
|
||||
}
|
||||
else if (clearExisting)
|
||||
{
|
||||
var filesToDelete = Directory.GetFiles(existingPath);
|
||||
foreach (string file in filesToDelete)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
//copy each files from temp folder to newPath
|
||||
var filesToUpload = Directory.GetFiles(documentPath);
|
||||
foreach (string file in filesToUpload)
|
||||
{
|
||||
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
|
||||
}
|
||||
}
|
||||
if (File.Exists(dataPath))
|
||||
{
|
||||
//data path will always exist as it is created on startup if not.
|
||||
File.Move(dataPath, StaticHelper.DbName, true);
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
//check if config folder exists.
|
||||
if (!Directory.Exists("config/"))
|
||||
{
|
||||
Directory.CreateDirectory("config/");
|
||||
}
|
||||
File.Move(configPath, StaticHelper.UserConfigPath, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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")}";
|
||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
||||
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
|
||||
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||
var dataPath = StaticHelper.DbName;
|
||||
var configPath = StaticHelper.UserConfigPath;
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
if (Directory.Exists(imagePath))
|
||||
{
|
||||
var files = Directory.GetFiles(imagePath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "images");
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
|
||||
}
|
||||
}
|
||||
if (Directory.Exists(documentPath))
|
||||
{
|
||||
var files = Directory.GetFiles(documentPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "documents");
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
|
||||
}
|
||||
}
|
||||
if (File.Exists(dataPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "data");
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "config");
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(configPath, $"{newPath}/{Path.GetFileName(configPath)}");
|
||||
}
|
||||
var destFilePath = $"{tempPath}.zip";
|
||||
ZipFile.CreateFromDirectory(tempPath, destFilePath);
|
||||
//delete temp directory
|
||||
Directory.Delete(tempPath, true);
|
||||
return $"/temp/{folderName}.zip";
|
||||
}
|
||||
public string MoveFileFromTemp(string currentFilePath, string newFolder)
|
||||
{
|
||||
string tempPath = "temp/";
|
||||
@@ -38,7 +204,8 @@
|
||||
{
|
||||
return currentFilePath;
|
||||
}
|
||||
if (currentFilePath.StartsWith("/")) {
|
||||
if (currentFilePath.StartsWith("/"))
|
||||
{
|
||||
currentFilePath = currentFilePath.Substring(1);
|
||||
}
|
||||
string uploadPath = Path.Combine(_webEnv.WebRootPath, newFolder);
|
||||
@@ -67,7 +234,8 @@
|
||||
if (!File.Exists(filePath)) //verify file no longer exists.
|
||||
{
|
||||
return true;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,33 @@ namespace CarCareTracker.Helper
|
||||
public interface IGasHelper
|
||||
{
|
||||
List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG);
|
||||
string GetAverageGasMileage(List<GasRecordViewModel> results, bool useMPG);
|
||||
}
|
||||
public class GasHelper : IGasHelper
|
||||
{
|
||||
public string GetAverageGasMileage(List<GasRecordViewModel> results, bool useMPG)
|
||||
{
|
||||
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 = recordsToCalculateGallons.Sum(x => x.Gallons);
|
||||
var deltaMileage = maxMileage - minMileage;
|
||||
var averageGasMileage = (maxMileage - minMileage) / totalGallonsConsumed;
|
||||
if (!useMPG)
|
||||
{
|
||||
averageGasMileage = 100 / averageGasMileage;
|
||||
}
|
||||
return averageGasMileage.ToString("F");
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
public List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG)
|
||||
{
|
||||
//need to order by to get correct results
|
||||
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||
var computedResults = new List<GasRecordViewModel>();
|
||||
int previousMileage = 0;
|
||||
decimal unFactoredConsumption = 0.00M;
|
||||
@@ -42,7 +64,10 @@ namespace CarCareTracker.Helper
|
||||
Gallons = convertedConsumption,
|
||||
Cost = currentObject.Cost,
|
||||
DeltaMileage = deltaMileage,
|
||||
CostPerGallon = currentObject.Cost / convertedConsumption
|
||||
CostPerGallon = convertedConsumption > 0.00M ? currentObject.Cost / convertedConsumption : 0,
|
||||
IsFillToFull = currentObject.IsFillToFull,
|
||||
MissedFuelUp = currentObject.MissedFuelUp,
|
||||
Notes = currentObject.Notes
|
||||
};
|
||||
if (currentObject.MissedFuelUp)
|
||||
{
|
||||
@@ -55,7 +80,10 @@ namespace CarCareTracker.Helper
|
||||
else if (currentObject.IsFillToFull)
|
||||
{
|
||||
//if user filled to full.
|
||||
gasRecordViewModel.MilesPerGallon = useMPG ? (unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption) : 100 / ((unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption));
|
||||
if (convertedConsumption > 0.00M)
|
||||
{
|
||||
gasRecordViewModel.MilesPerGallon = useMPG ? (unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption) : 100 / ((unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption));
|
||||
}
|
||||
//reset unFactored vars
|
||||
unFactoredConsumption = 0;
|
||||
unFactoredMileage = 0;
|
||||
@@ -81,7 +109,10 @@ namespace CarCareTracker.Helper
|
||||
Cost = currentObject.Cost,
|
||||
DeltaMileage = 0,
|
||||
MilesPerGallon = 0,
|
||||
CostPerGallon = currentObject.Cost / convertedConsumption
|
||||
CostPerGallon = convertedConsumption > 0.00M ? currentObject.Cost / convertedConsumption : 0,
|
||||
IsFillToFull = currentObject.IsFillToFull,
|
||||
MissedFuelUp = currentObject.MissedFuelUp,
|
||||
Notes = currentObject.Notes
|
||||
});
|
||||
}
|
||||
previousMileage = currentObject.Mileage;
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
OperationResponse NotifyUserForRegistration(string emailAddress, string token);
|
||||
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
||||
OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders);
|
||||
}
|
||||
public class MailHelper : IMailHelper
|
||||
{
|
||||
@@ -60,20 +61,62 @@ namespace CarCareTracker.Helper
|
||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||
}
|
||||
}
|
||||
private bool SendEmail(string emailTo, string emailSubject, string emailBody) {
|
||||
public OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
|
||||
}
|
||||
if (!emailAddresses.Any())
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "No recipients could be found" };
|
||||
}
|
||||
if (!reminders.Any())
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "No reminders could be found" };
|
||||
}
|
||||
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
|
||||
//construct html table.
|
||||
string emailBody = $"<h4>{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate}</h4><br /><table style='width:100%'><tr><th style='padding:8px;'>Urgency</th><th style='padding:8px;'>Description</th></tr>";
|
||||
foreach(ReminderRecordViewModel reminder in reminders)
|
||||
{
|
||||
emailBody += $"<tr><td style='padding:8px; text-align:center;'>{reminder.Urgency}</td><td style='padding:8px; text-align:center;'>{reminder.Description}</td></tr>";
|
||||
}
|
||||
emailBody += "</table>";
|
||||
try
|
||||
{
|
||||
foreach (string emailAddress in emailAddresses)
|
||||
{
|
||||
SendEmail(emailAddress, emailSubject, emailBody, true, true);
|
||||
}
|
||||
return new OperationResponse { Success = true, Message = "Email Sent!" };
|
||||
} catch (Exception ex)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = ex.Message };
|
||||
}
|
||||
}
|
||||
private bool SendEmail(string emailTo, string emailSubject, string emailBody, bool isBodyHtml = false, bool useAsync = false) {
|
||||
string to = emailTo;
|
||||
string from = mailConfig.EmailFrom;
|
||||
var server = mailConfig.EmailServer;
|
||||
MailMessage message = new MailMessage(from, to);
|
||||
message.Subject = emailSubject;
|
||||
message.Body = emailBody;
|
||||
message.IsBodyHtml = isBodyHtml;
|
||||
SmtpClient client = new SmtpClient(server);
|
||||
client.EnableSsl = mailConfig.UseSSL;
|
||||
client.Port = mailConfig.Port;
|
||||
client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password);
|
||||
try
|
||||
{
|
||||
client.Send(message);
|
||||
if (useAsync)
|
||||
{
|
||||
client.SendMailAsync(message, new CancellationToken());
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Send(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -4,10 +4,28 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
public interface IReminderHelper
|
||||
{
|
||||
ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder);
|
||||
List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare);
|
||||
}
|
||||
public class ReminderHelper: IReminderHelper
|
||||
{
|
||||
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder)
|
||||
{
|
||||
if (existingReminder.Metric == ReminderMetric.Both)
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||
existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval;
|
||||
}
|
||||
else if (existingReminder.Metric == ReminderMetric.Odometer)
|
||||
{
|
||||
existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval;
|
||||
}
|
||||
else if (existingReminder.Metric == ReminderMetric.Date)
|
||||
{
|
||||
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
|
||||
}
|
||||
return existingReminder;
|
||||
}
|
||||
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare)
|
||||
{
|
||||
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
||||
@@ -21,7 +39,8 @@ namespace CarCareTracker.Helper
|
||||
Mileage = reminder.Mileage,
|
||||
Description = reminder.Description,
|
||||
Notes = reminder.Notes,
|
||||
Metric = reminder.Metric
|
||||
Metric = reminder.Metric,
|
||||
IsRecurring = reminder.IsRecurring
|
||||
};
|
||||
if (reminder.Metric == ReminderMetric.Both)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace CarCareTracker.Helper
|
||||
using CarCareTracker.Models;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// helper method for static vars
|
||||
@@ -18,10 +21,124 @@
|
||||
if (input.Length > maxLength)
|
||||
{
|
||||
return (input.Substring(0, maxLength) + "...");
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
public static string DefaultActiveTab(UserConfig userConfig, ImportMode tab)
|
||||
{
|
||||
var defaultTab = userConfig.DefaultTab;
|
||||
var visibleTabs = userConfig.VisibleTabs;
|
||||
if (visibleTabs.Contains(tab) && tab == defaultTab)
|
||||
{
|
||||
return "active";
|
||||
}
|
||||
else if (!visibleTabs.Contains(tab))
|
||||
{
|
||||
return "d-none";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
public static string DefaultActiveTabContent(UserConfig userConfig, ImportMode tab)
|
||||
{
|
||||
var defaultTab = userConfig.DefaultTab;
|
||||
if (tab == defaultTab)
|
||||
{
|
||||
return "show active";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
public static string DefaultTabSelected(UserConfig userConfig, ImportMode tab)
|
||||
{
|
||||
var defaultTab = userConfig.DefaultTab;
|
||||
var visibleTabs = userConfig.VisibleTabs;
|
||||
if (!visibleTabs.Contains(tab))
|
||||
{
|
||||
return "disabled";
|
||||
}
|
||||
else if (tab == defaultTab)
|
||||
{
|
||||
return "selected";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
public static List<CostForVehicleByMonth> GetBaseLineCosts()
|
||||
{
|
||||
return new List<CostForVehicleByMonth>()
|
||||
{
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(1), MonthId = 1, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(2), MonthId = 2, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(3), MonthId = 3, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(4), MonthId = 4, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(5), MonthId = 5, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(6), MonthId = 6, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(7), MonthId = 7, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(8), MonthId = 8, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(9), MonthId = 9, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(10), MonthId = 10, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(11), MonthId = 11, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(12), MonthId = 12, Cost = 0M}
|
||||
};
|
||||
}
|
||||
public static List<CostForVehicleByMonth> GetBaseLineCostsNoMonthName()
|
||||
{
|
||||
return new List<CostForVehicleByMonth>()
|
||||
{
|
||||
new CostForVehicleByMonth { MonthId = 1, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 2, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 3, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 4, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 5, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 6, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 7, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 8, Cost = 0M},
|
||||
new CostForVehicleByMonth {MonthId = 9, Cost = 0M},
|
||||
new CostForVehicleByMonth { MonthId = 10, Cost = 0M},
|
||||
new CostForVehicleByMonth { MonthId = 11, Cost = 0M},
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
LICENSE
9
LICENSE
@@ -1,8 +1,7 @@
|
||||
LubeLogger by Hargata Softworks is licensed under the MIT License individual
|
||||
personal use. Commercial users and/or corporate entities are required
|
||||
to pay either a one time fee or maintain an active subscription in order to
|
||||
continue using LubeLogger. For pricing information please contact us at
|
||||
hargatasoftworks@gmail.com
|
||||
LubeLogger by Hargata Softworks is licensed under the MIT License for individual
|
||||
and personal use. Commercial users and/or corporate entities are required
|
||||
to maintain an active subscription in order to continue using LubeLogger.
|
||||
For pricing information please contact us at hargatasoftworks@gmail.com
|
||||
|
||||
MIT License
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ using CsvHelper.Configuration;
|
||||
|
||||
namespace CarCareTracker.MapProfile
|
||||
{
|
||||
public class FuellyMapper: ClassMap<ImportModel>
|
||||
public class ImportMapper: ClassMap<ImportModel>
|
||||
{
|
||||
public FuellyMapper()
|
||||
public ImportMapper()
|
||||
{
|
||||
Map(m => m.Date).Name(["date", "fuelup_date"]);
|
||||
Map(m => m.DateCreated).Name(["datecreated"]);
|
||||
Map(m => m.DateModified).Name(["datemodified"]);
|
||||
Map(m => m.Odometer).Name(["odometer"]);
|
||||
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
|
||||
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
|
||||
@@ -16,7 +18,13 @@ namespace CarCareTracker.MapProfile
|
||||
Map(m => m.PartialFuelUp).Name(["partial_fuelup"]);
|
||||
Map(m => m.IsFillToFull).Name(["isfilltofull", "filled up"]);
|
||||
Map(m => m.Description).Name(["description"]);
|
||||
Map(m => m.MissedFuelUp).Name(["missed_fuelup"]);
|
||||
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup"]);
|
||||
Map(m => m.PartSupplier).Name(["partsupplier"]);
|
||||
Map(m => m.PartQuantity).Name(["partquantity"]);
|
||||
Map(m => m.PartNumber).Name(["partnumber"]);
|
||||
Map(m => m.Progress).Name(["progress"]);
|
||||
Map(m => m.Type).Name(["type"]);
|
||||
Map(m => m.Priority).Name(["priority"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,5 +160,18 @@ namespace CarCareTracker.Middleware
|
||||
Response.Redirect("/Login/Index");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
|
||||
{
|
||||
if (Request.RouteValues.TryGetValue("controller", out object value))
|
||||
{
|
||||
if (value.ToString().ToLower() == "api")
|
||||
{
|
||||
Response.StatusCode = 403;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Response.Redirect("/Error/401");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string Date { get; set; }
|
||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||
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>();
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public CollisionRecord ToCollisionRecord() { return new CollisionRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
public decimal Cost { get; set; }
|
||||
public bool IsFillToFull { get; set; } = true;
|
||||
public bool MissedFuelUp { get; set; } = false;
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string Date { get; set; }
|
||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||
/// <summary>
|
||||
/// American moment
|
||||
/// </summary>
|
||||
@@ -16,6 +16,7 @@
|
||||
public decimal Cost { get; set; }
|
||||
public bool IsFillToFull { get; set; } = true;
|
||||
public bool MissedFuelUp { get; set; } = false;
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public GasRecord ToGasRecord() { return new GasRecord {
|
||||
Id = Id,
|
||||
@@ -26,7 +27,8 @@
|
||||
VehicleId = VehicleId,
|
||||
Files = Files,
|
||||
IsFillToFull = IsFillToFull,
|
||||
MissedFuelUp = MissedFuelUp
|
||||
MissedFuelUp = MissedFuelUp,
|
||||
Notes = Notes
|
||||
}; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,8 @@
|
||||
public int DeltaMileage { get; set; }
|
||||
public decimal MilesPerGallon { get; set; }
|
||||
public decimal CostPerGallon { get; set; }
|
||||
public bool IsFillToFull { get; set; }
|
||||
public bool MissedFuelUp { get; set; }
|
||||
public string Notes { 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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class UpgradeRecord
|
||||
public class GenericRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
@@ -6,6 +6,11 @@
|
||||
public class ImportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string DateCreated { get; set; }
|
||||
public string DateModified { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Priority { get; set; }
|
||||
public string Progress { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
@@ -15,6 +20,20 @@
|
||||
public string PartialFuelUp { get; set; }
|
||||
public string IsFillToFull { get; set; }
|
||||
public string MissedFuelUp { get; set; }
|
||||
public string PartNumber { get; set; }
|
||||
public string PartSupplier { get; set; }
|
||||
public string PartQuantity { get; set; }
|
||||
}
|
||||
|
||||
public class SupplyRecordExportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string PartNumber { get; set; }
|
||||
public string PartSupplier { get; set; }
|
||||
public string PartQuantity { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceRecordExportModel
|
||||
@@ -25,10 +44,15 @@
|
||||
public string Notes { get; set; }
|
||||
public string Cost { get; set; }
|
||||
}
|
||||
public class TaxRecordExportModel
|
||||
public class OdometerRecordExportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
public string Notes { get; set; }
|
||||
}
|
||||
public class TaxRecordExportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Cost { get; set; }
|
||||
@@ -40,6 +64,9 @@
|
||||
public string FuelConsumed { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public string FuelEconomy { get; set; }
|
||||
public string IsFillToFull { get; set; }
|
||||
public string MissedFuelUp { get; set; }
|
||||
public string Notes { get; set; }
|
||||
}
|
||||
public class ReminderExportModel
|
||||
{
|
||||
@@ -48,4 +75,16 @@
|
||||
public string Metric { get; set; }
|
||||
public string Notes { get; set; }
|
||||
}
|
||||
public class PlanRecordExportModel
|
||||
{
|
||||
public string DateCreated { get; set; }
|
||||
public string DateModified { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Priority { get; set; }
|
||||
public string Progress { get; set; }
|
||||
public string Cost { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
public int VehicleId { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string NoteText { get; set; }
|
||||
public bool Pinned { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
12
Models/OdometerRecord/OdometerRecord.cs
Normal file
12
Models/OdometerRecord/OdometerRecord.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class OdometerRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public int Mileage { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
}
|
||||
13
Models/OdometerRecord/OdometerRecordInput.cs
Normal file
13
Models/OdometerRecord/OdometerRecordInput.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class OdometerRecordInput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||
public int Mileage { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files }; }
|
||||
}
|
||||
}
|
||||
8
Models/PlanRecord/PlanCostItem.cs
Normal file
8
Models/PlanRecord/PlanCostItem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class PlanCostItem
|
||||
{
|
||||
public string CostName { get; set; }
|
||||
public decimal CostAmount { get; set; }
|
||||
}
|
||||
}
|
||||
17
Models/PlanRecord/PlanRecord.cs
Normal file
17
Models/PlanRecord/PlanRecord.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class PlanRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public DateTime DateCreated { get; set; }
|
||||
public DateTime DateModified { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public ImportMode ImportMode { get; set; }
|
||||
public PlanPriority Priority { get; set; }
|
||||
public PlanProgress Progress { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
}
|
||||
}
|
||||
31
Models/PlanRecord/PlanRecordInput.cs
Normal file
31
Models/PlanRecord/PlanRecordInput.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class PlanRecordInput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string DateCreated { get; set; } = DateTime.Now.ToShortDateString();
|
||||
public string DateModified { get; set; } = DateTime.Now.ToShortDateString();
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public ImportMode ImportMode { get; set; }
|
||||
public PlanPriority Priority { get; set; }
|
||||
public PlanProgress Progress { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public PlanRecord ToPlanRecord() { return new PlanRecord {
|
||||
Id = Id,
|
||||
VehicleId = VehicleId,
|
||||
DateCreated = DateTime.Parse(DateCreated),
|
||||
DateModified = DateTime.Parse(DateModified),
|
||||
Description = Description,
|
||||
Notes = Notes,
|
||||
Files = Files,
|
||||
ImportMode = ImportMode,
|
||||
Cost = Cost,
|
||||
Priority = Priority,
|
||||
Progress = Progress
|
||||
}; }
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
public int Mileage { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
public int Mileage { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
public ReminderRecord ToReminderRecord() { return new ReminderRecord {
|
||||
Id = Id,
|
||||
@@ -16,6 +19,9 @@
|
||||
Mileage = Mileage,
|
||||
Description = Description,
|
||||
Metric = Metric,
|
||||
IsRecurring = IsRecurring,
|
||||
ReminderMileageInterval = ReminderMileageInterval,
|
||||
ReminderMonthInterval = ReminderMonthInterval,
|
||||
Notes = Notes }; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,9 @@
|
||||
/// </summary>
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
public ReminderUrgency Urgency { get; set; } = ReminderUrgency.NotUrgent;
|
||||
/// <summary>
|
||||
/// Recurring Reminders
|
||||
/// </summary>
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
public Vehicle VehicleData { get; set; }
|
||||
public List<GenericReportModel> VehicleHistory { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
public decimal MPG { get; set; }
|
||||
public string MPG { get; set; }
|
||||
public decimal TotalCost { get; set; }
|
||||
public decimal TotalGasCost { get; set; }
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string Date { get; set; }
|
||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||
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>();
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||
}
|
||||
}
|
||||
|
||||
37
Models/Supply/SupplyRecord.cs
Normal file
37
Models/Supply/SupplyRecord.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
/// <summary>
|
||||
/// When the part or supplies were purchased.
|
||||
/// </summary>
|
||||
public DateTime Date { get; set; }
|
||||
/// <summary>
|
||||
/// Part number can be alphanumeric.
|
||||
/// </summary>
|
||||
public string PartNumber { get; set; }
|
||||
/// <summary>
|
||||
/// Where the part/supplies were purchased from.
|
||||
/// </summary>
|
||||
public string PartSupplier { get; set; }
|
||||
/// <summary>
|
||||
/// Amount purchased, can be partial quantities such as fluids.
|
||||
/// </summary>
|
||||
public decimal Quantity { get; set; }
|
||||
/// <summary>
|
||||
/// Description of the part/supplies purchased.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// How much it costs
|
||||
/// </summary>
|
||||
public decimal Cost { get; set; }
|
||||
/// <summary>
|
||||
/// Additional notes.
|
||||
/// </summary>
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
}
|
||||
27
Models/Supply/SupplyRecordInput.cs
Normal file
27
Models/Supply/SupplyRecordInput.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyRecordInput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||
public string PartNumber { get; set; }
|
||||
public string PartSupplier { get; set; }
|
||||
public decimal Quantity { 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>();
|
||||
public SupplyRecord ToSupplyRecord() { return new SupplyRecord {
|
||||
Id = Id,
|
||||
VehicleId = VehicleId,
|
||||
Date = DateTime.Parse(Date),
|
||||
Cost = Cost,
|
||||
PartNumber = PartNumber,
|
||||
PartSupplier = PartSupplier,
|
||||
Quantity = Quantity,
|
||||
Description = Description,
|
||||
Notes = Notes,
|
||||
Files = Files }; }
|
||||
}
|
||||
}
|
||||
7
Models/Supply/SupplyUsage.cs
Normal file
7
Models/Supply/SupplyUsage.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyUsage {
|
||||
public int SupplyId { get; set; }
|
||||
public decimal Quantity { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
public string Description { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,22 @@
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string Date { get; set; }
|
||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||
public string Description { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public TaxRecord ToTaxRecord() { return new TaxRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Description = Description, Notes = Notes, Files = Files }; }
|
||||
public TaxRecord ToTaxRecord() { return new TaxRecord {
|
||||
Id = Id,
|
||||
VehicleId = VehicleId,
|
||||
Date = DateTime.Parse(Date),
|
||||
Cost = Cost,
|
||||
Description = Description,
|
||||
Notes = Notes,
|
||||
IsRecurring = IsRecurring,
|
||||
RecurringInterval = RecurringInterval,
|
||||
Files = Files }; }
|
||||
}
|
||||
}
|
||||
|
||||
6
Models/UpgradeRecord/UpgradeRecord.cs
Normal file
6
Models/UpgradeRecord/UpgradeRecord.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class UpgradeRecord: GenericRecord
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,13 @@
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int VehicleId { get; set; }
|
||||
public string Date { get; set; }
|
||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||
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>();
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,21 @@
|
||||
public bool EnableAuth { get; set; }
|
||||
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; }
|
||||
public string UserPasswordHash { get; set;}
|
||||
public List<ImportMode> VisibleTabs { get; set; } = new List<ImportMode>() {
|
||||
ImportMode.Dashboard,
|
||||
ImportMode.ServiceRecord,
|
||||
ImportMode.RepairRecord,
|
||||
ImportMode.GasRecord,
|
||||
ImportMode.UpgradeRecord,
|
||||
ImportMode.TaxRecord,
|
||||
ImportMode.ReminderRecord,
|
||||
ImportMode.NoteRecord};
|
||||
public ImportMode DefaultTab { get; set; } = ImportMode.Dashboard;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ builder.Services.AddSingleton<IUserRecordDataAccess, UserRecordDataAccess>();
|
||||
builder.Services.AddSingleton<ITokenRecordDataAccess, TokenRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IUserAccessDataAccess, UserAccessDataAccess>();
|
||||
builder.Services.AddSingleton<IUserConfigDataAccess, UserConfigDataAccess>();
|
||||
builder.Services.AddSingleton<ISupplyRecordDataAccess, SupplyRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IPlanRecordDataAccess, PlanRecordDataAccess>();
|
||||
builder.Services.AddSingleton<IOdometerRecordDataAccess, OdometerRecordDataAccess>();
|
||||
|
||||
//configure helpers
|
||||
builder.Services.AddSingleton<IFileHelper, FileHelper>();
|
||||
|
||||
17
README.md
17
README.md
@@ -2,9 +2,21 @@
|
||||
|
||||
A self-hosted, open-source vehicle service records and maintainence tracker.
|
||||
|
||||
Visit our website: https://lubelogger.com
|
||||
|
||||
Support this project on Patreon: https://patreon.com/LubeLogger
|
||||
|
||||
## Why
|
||||
Because nobody should have to deal with a homemade spreadsheet or a shoebox full of receipts when it comes to vehicle maintainence.
|
||||
|
||||
## Screenshots
|
||||
<a href="/docs/screenshots.md">Screenshots</a>
|
||||
|
||||
## Demo
|
||||
Try it out before you download it! The live demo resets every 20 minutes.
|
||||
|
||||
[Live Demo](https://demo.lubelogger.com) Login using username "test" and password "1234"
|
||||
|
||||
## Dependencies
|
||||
- Bootstrap
|
||||
- LiteDB
|
||||
@@ -12,12 +24,13 @@ Because nobody should have to deal with a homemade spreadsheet or a shoebox full
|
||||
- SweetAlert2
|
||||
- CsvHelper
|
||||
- Chart.js
|
||||
- Drawdown
|
||||
|
||||
## Docker Setup (GHCR)
|
||||
1. Install Docker
|
||||
2. Run `docker pull ghcr.io/hargata/lubelogger:latest`
|
||||
3. CHECK culture in .env file, default is en_US, this will change the currency and date formats. You can also setup SMTP Config here.
|
||||
4. If not using traefik, use docker-compose-notraefik.yml
|
||||
4. If using traefik, use docker-compose.traefik.yml
|
||||
5. Run `docker-compose up`
|
||||
|
||||
## Docker Setup (Manual Build)
|
||||
@@ -26,7 +39,7 @@ Because nobody should have to deal with a homemade spreadsheet or a shoebox full
|
||||
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
|
||||
4. Run `docker build -t lubelogger -f Dockerfile .`
|
||||
5. CHECK docker-compose.yml and make sure the mounting directories look correct.
|
||||
6. If not using traefik, use docker-compose-notraefik.yml
|
||||
6. If using traefik, use docker-compose.traefik.yml
|
||||
7. Run `docker-compose up`
|
||||
|
||||
## Additional Docker Instructions
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<div class="row">
|
||||
@{
|
||||
ViewData["Title"] = "LubeLogger API";
|
||||
}
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-center">
|
||||
<h6 class="display-6 mt-2">API</h6>
|
||||
</div>
|
||||
@@ -51,6 +54,28 @@
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/servicerecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Service Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
description - Description<br/>
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
@@ -65,6 +90,28 @@
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/repairrecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Repair Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
@@ -79,6 +126,28 @@
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/upgraderecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Upgrade Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
@@ -93,6 +162,27 @@
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/taxrecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Tax Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
description - Description<br />
|
||||
cost - Cost<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
@@ -111,6 +201,30 @@
|
||||
useUKMPG(bool) - Use UK Imperial Calculation
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/gasrecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Gas Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
fuelConsumed - Fuel Consumed<br />
|
||||
cost - Cost<br />
|
||||
isFillToFull(bool) - Filled To Full<br />
|
||||
missedFuelUp(bool) - Missed Fuel Up<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
@@ -124,4 +238,70 @@
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/reminders/send</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Send reminder emails out to collaborators based on specified urgency.
|
||||
</div>
|
||||
<div class="col-3">
|
||||
(must be root user)<br />
|
||||
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/makebackup</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Creates a snapshot/backup of the DB at the time and returns a URL to download it.
|
||||
</div>
|
||||
<div class="col-3">
|
||||
No Params(must be root user)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
GET
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/odometerrecords</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Returns a list of odometer records for the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
POST
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<code>/api/vehicle/odometerrecords/add</code>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
Adds Odometer Record to the vehicle
|
||||
</div>
|
||||
<div class="col-3">
|
||||
vehicleId - Id of Vehicle
|
||||
<br />
|
||||
Body(form-data): {<br />
|
||||
date - Date to be entered<br />
|
||||
odometer - Odometer reading<br />
|
||||
notes - notes(optional)<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-4">Token</th>
|
||||
<th scope="col" class="col-6">Issued To</th>
|
||||
@@ -62,7 +62,7 @@
|
||||
<span class="lead">Users</span>
|
||||
<hr />
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-4">Username</th>
|
||||
<th scope="col" class="col-4">Email</th>
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
|
||||
<ul class="navbar-nav" id="homeTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-car-front me-2"></i>Garage</span></button>
|
||||
<button class="nav-link @(Model == "garage" ? "active" : "")" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-car-front me-2"></i>Garage</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>Settings</span></button>
|
||||
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>Settings</span></button>
|
||||
</li>
|
||||
@if (User.IsInRole("CookieAuth"))
|
||||
{
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@model UserConfig
|
||||
@using CarCareTracker.Helper
|
||||
@model UserConfig
|
||||
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-center">
|
||||
@@ -22,8 +23,6 @@
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUKMPG" checked="@Model.UseUKMPG">
|
||||
<label class="form-check-label" for="useUKMPG">Use UK MPG Calculation<br /><small class="text-body-secondary">Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UseDescending">
|
||||
<label class="form-check-label" for="useDescending">Sort lists in Descending Order(Newest to Oldest)</label>
|
||||
@@ -32,6 +31,22 @@
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.HideZero">
|
||||
<label class="form-check-label" for="hideZero">Replace @(0.ToString("C")) Costs with ---</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<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>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoOdometerInsert" checked="@Model.EnableAutoOdometerInsert">
|
||||
<label class="form-check-label" for="enableAutoOdometerInsert">Auto Insert Odometer Records<br /><small class="text-body-secondary">Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan</small></label>
|
||||
</div>
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<div class="form-check form-switch">
|
||||
@@ -40,6 +55,98 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="row" id="visibleTabs">
|
||||
<div class="col-12">
|
||||
<span class="lead">Visible Tabs</span>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ServiceRecord" id="serviceRecordTab" @(Model.VisibleTabs.Contains(ImportMode.ServiceRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="serviceRecordTab">Service Records</label>
|
||||
</li>
|
||||
<li class="list-group-item d-none">
|
||||
<input onChange="updateSettings()" disabled class="form-check-input me-1" type="checkbox" value="Dashboard" id="dashboardTab" @(Model.VisibleTabs.Contains(ImportMode.Dashboard) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="dashboardTab">Dashboard</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="RepairRecord" id="repairRecordTab" @(Model.VisibleTabs.Contains(ImportMode.RepairRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="repairRecordTab">Repairs</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="UpgradeRecord" id="upgradeRecordTab" @(Model.VisibleTabs.Contains(ImportMode.UpgradeRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="upgradeRecordTab">Upgrades</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="GasRecord" id="gasRecordTab" @(Model.VisibleTabs.Contains(ImportMode.GasRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="gasRecordTab">Fuel</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="OdometerRecord" id="odometerRecordTab" @(Model.VisibleTabs.Contains(ImportMode.OdometerRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="odometerRecordTab">Odometer</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="TaxRecord" id="taxRecordTab" @(Model.VisibleTabs.Contains(ImportMode.TaxRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="taxRecordTab">Taxes</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="NoteRecord" id="noteRecordTab" @(Model.VisibleTabs.Contains(ImportMode.NoteRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="noteRecordTab">Notes</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ReminderRecord" id="reminderRecordTab" @(Model.VisibleTabs.Contains(ImportMode.ReminderRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="reminderRecordTab">Reminder</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="SupplyRecord" id="supplyRecordTab" @(Model.VisibleTabs.Contains(ImportMode.SupplyRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="supplyRecordTab">Supplies</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="PlanRecord" id="planRecordTab" @(Model.VisibleTabs.Contains(ImportMode.PlanRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="planRecordTab">Planner</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="lead">Default Tab</span>
|
||||
<select class="form-select" onchange="updateSettings()" id="defaultTab">
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.Dashboard)) value="Dashboard">Dashboard</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model,ImportMode.ServiceRecord)) value="ServiceRecord">Service Record</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.RepairRecord)) value="RepairRecord">Repairs</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.UpgradeRecord)) value="UpgradeRecord">Upgrades</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.GasRecord)) value="GasRecord">Fuel</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.TaxRecord)) value="TaxRecord">Tax</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.NoteRecord)) value="NoteRecord">Notes</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.ReminderRecord)) value="ReminderRecord">Reminders</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.SupplyRecord)) value="SupplyRecord">Supplies</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.PlanRecord)) value="PlanRecord">Planner</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.OdometerRecord)) value="OdometerRecord">Odometer</!option>
|
||||
</select>
|
||||
</div>
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="lead">Backups</span>
|
||||
<div class="row">
|
||||
<div class="col-6 d-grid">
|
||||
<button onclick="makeBackup()" class="btn btn-primary btn-md">Make</button>
|
||||
</div>
|
||||
<div class="col-6 d-grid">
|
||||
<input onChange="restoreBackup(this)" type="file" accept=".zip" class="d-none" id="inputBackup">
|
||||
<button onclick="openRestoreBackup()" class="btn btn-secondary btn-md">Restore</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-center">
|
||||
@@ -50,10 +157,16 @@
|
||||
<div class="d-flex justify-content-center">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<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.
|
||||
</p>
|
||||
<p class="lead">If you enjoyed using this app, please consider spreading the good word.</p>
|
||||
<p class="lead">
|
||||
If you enjoyed using this app, please consider spreading the good word.<br />
|
||||
If you are a commercial user, or if you just want to support the development of this project, consider subscribing to <a class="link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" href="https://www.patreon.com/LubeLogger" target="_blank">our Patreon</a>
|
||||
</p>
|
||||
<div class="d-flex justify-content-center">
|
||||
<h6 class="display-7 mt-2">Hometown Shoutout</h6>
|
||||
</div>
|
||||
@@ -78,20 +191,40 @@
|
||||
<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>
|
||||
<script>
|
||||
function getCheckedTabs() {
|
||||
var visibleTabs = $("#visibleTabs :checked").map(function () {
|
||||
return this.value;
|
||||
});
|
||||
return visibleTabs.toArray();
|
||||
}
|
||||
function updateSettings(){
|
||||
var visibleTabs = getCheckedTabs();
|
||||
var defaultTab = $("#defaultTab").val();
|
||||
if (!visibleTabs.includes(defaultTab)){
|
||||
defaultTab = "Dashboard"; //default to dashboard.
|
||||
}
|
||||
var userConfigObject = {
|
||||
useDarkMode: $("#enableDarkMode").is(':checked'),
|
||||
enableCsvImports: $("#enableCsvImports").is(':checked'),
|
||||
useMPG: $("#useMPG").is(':checked'),
|
||||
useDescending: $("#useDescending").is(':checked'),
|
||||
hideZero: $("#hideZero").is(":checked"),
|
||||
useUKMpg: $("#useUKMPG").is(":checked")
|
||||
useUKMpg: $("#useUKMPG").is(":checked"),
|
||||
useThreeDecimalGasCost: $("#useThreeDecimal").is(":checked"),
|
||||
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
|
||||
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
|
||||
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
|
||||
visibleTabs: visibleTabs,
|
||||
defaultTab: defaultTab
|
||||
}
|
||||
sloader.show();
|
||||
$.post('/Home/WriteToSettings', { userConfig: userConfigObject}, function(data){
|
||||
sloader.hide();
|
||||
if (data) {
|
||||
setTimeout(function () { window.location.href = '/Home/Index?tab=settings' }, 500);
|
||||
} else {
|
||||
@@ -99,6 +232,40 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
function makeBackup() {
|
||||
$.get('/Files/MakeBackup', function (data) {
|
||||
window.location.href = data;
|
||||
});
|
||||
}
|
||||
function openRestoreBackup(){
|
||||
$("#inputBackup").click();
|
||||
}
|
||||
function restoreBackup(event) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", event.files[0]);
|
||||
sloader.show();
|
||||
$.ajax({
|
||||
url: "/Files/HandleFileUpload",
|
||||
data: formData,
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
success: function (response) {
|
||||
if (response.trim() != '') {
|
||||
$.post('/Files/RestoreBackup', { fileName : response}, function (data) {
|
||||
sloader.hide();
|
||||
if (data){
|
||||
successToast("Backup Restored");
|
||||
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
|
||||
} else {
|
||||
errorToast("An error occurred, please try again later.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function enableAuthCheckChanged(){
|
||||
var enableAuth = $("#enableAuth").is(":checked");
|
||||
if (enableAuth) {
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
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();
|
||||
if (!shortDatePattern.Contains("dd"))
|
||||
{
|
||||
@@ -46,7 +48,8 @@
|
||||
function getGlobalConfig() {
|
||||
return {
|
||||
useDarkMode : "@useDarkMode" == "True",
|
||||
enableCsvImport : "@enableCsvImports" == "True"
|
||||
enableCsvImport : "@enableCsvImports" == "True",
|
||||
useMarkDown: "@useMarkDown" == "True"
|
||||
}
|
||||
}
|
||||
function getShortDatePattern() {
|
||||
@@ -54,6 +57,27 @@
|
||||
pattern: "@shortDatePattern"
|
||||
}
|
||||
}
|
||||
function globalParseFloat(input){
|
||||
//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) {
|
||||
var decimalSeparator = "@numberFormat.NumberDecimalSeparator";
|
||||
input = input.replace(".", decimalSeparator);
|
||||
return input;
|
||||
}
|
||||
</script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</head>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
@{
|
||||
@using CarCareTracker.Helper
|
||||
@{
|
||||
ViewData["Title"] = "LubeLogger - View Vehicle";
|
||||
}
|
||||
@inject IConfigHelper config
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
}
|
||||
@model Vehicle
|
||||
@section Scripts {
|
||||
<script src="~/js/vehicle.js" asp-append-version="true"></script>
|
||||
@@ -12,7 +17,11 @@
|
||||
<script src="~/js/upgraderecord.js" asp-append-version="true"></script>
|
||||
<script src="~/js/note.js" asp-append-version="true"></script>
|
||||
<script src="~/js/reports.js" asp-append-version="true"></script>
|
||||
<script src="~/js/supplyrecord.js" asp-append-version="true"></script>
|
||||
<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">
|
||||
@@ -23,28 +32,37 @@
|
||||
<button class="nav-link" onclick="editVehicle(@Model.Id)"><span class="display-3 ms-2"><i class="bi bi-pencil-square"></i>Edit Vehicle</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>Dashboard</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>Dashboard</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>Service Records</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-bar-chart-steps me-2"></i>Planner</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-speedometer me-2"></i>Odometer</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>Service Records</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-fuel-pump me-2"></i>Fuel</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-currency-dollar me-2"></i>Taxes</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-journal-bookmark me-2"></i>Notes</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-fuel-pump me-2"></i>Fuel</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</span></button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-shop me-2"></i>Supplies</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-currency-dollar me-2"></i>Taxes</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-journal-bookmark me-2"></i>Notes</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button onclick="deleteVehicle(@Model.Id)" class="dropdown-item"><span class="display-3 ms-2"><i class="bi bi-trash me-2"></i>Delete Vehicle</span></button>
|
||||
@@ -65,28 +83,37 @@
|
||||
<hr />
|
||||
<ul class="nav nav-tabs lubelogger-tab" id="vehicleTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Dash</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Dash</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-bar-chart-steps me-2"></i>Planner</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-speedometer me-2"></i>Odometer</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump me-2"></i>Fuel</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar me-2"></i>Taxes</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark me-2"></i>Notes</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump me-2"></i>Fuel</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</button>
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop me-2"></i>Supplies</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar me-2"></i>Taxes</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark me-2"></i>Notes</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</button>
|
||||
</li>
|
||||
<li class="nav-item dropdown ms-auto" role="presentation">
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Manage Vehicle</a>
|
||||
@@ -96,14 +123,17 @@
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="vehicleTabContent">
|
||||
<div class="tab-pane fade" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade" id="notes-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade" id="reminder-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade show active" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.GasRecord)" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.TaxRecord)" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.NoteRecord)" id="notes-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.RepairRecord)" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ReminderRecord)" id="reminder-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.Dashboard)" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.SupplyRecord)" id="supply-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.PlanRecord)" id="plan-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.OdometerRecord)" id="odometer-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
|
||||
@@ -124,9 +154,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="inputSuppliesModal" tabindex="-1" role="dialog" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="inputSuppliesModalContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function GetVehicleId() {
|
||||
return { vehicleId: @Model.Id};
|
||||
}
|
||||
function GetDefaultTab() {
|
||||
return { tab: "@userConfig.DefaultTab" };
|
||||
}
|
||||
bindWindowResize();
|
||||
</script>
|
||||
@@ -25,6 +25,15 @@
|
||||
} else if (Model == ImportMode.TaxRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/taxrecordsample.csv" target="_blank">Download Sample</a>
|
||||
} else if (Model == ImportMode.SupplyRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/supplysample.csv" target="_blank">Download Sample</a>
|
||||
} else if (Model == ImportMode.PlanRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/plansample.csv" target="_blank">Download Sample</a>
|
||||
} else if (Model == ImportMode.OdometerRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/odometersample.csv" target="_blank">Download Sample</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,6 +71,12 @@
|
||||
getVehicleTaxRecords(vehicleId);
|
||||
} else if (mode == "UpgradeRecord") {
|
||||
getVehicleUpgradeRecords(vehicleId);
|
||||
} else if (mode == "SupplyRecord") {
|
||||
getVehicleSupplyRecords(vehicleId);
|
||||
} else if (mode == "PlanRecord"){
|
||||
getVehiclePlanRecords(vehicleId);
|
||||
} else if (mode == "OdometerRecord") {
|
||||
getVehicleOdometerRecords(vehicleId);
|
||||
}
|
||||
} else {
|
||||
errorToast("An error has occurred, please double check the data and try again.");
|
||||
|
||||
@@ -23,9 +23,13 @@
|
||||
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="Description of item(s) repaired(i.e. Alternator)" value="@Model.Description">
|
||||
<label for="collisionRecordCost">Cost</label>
|
||||
<input type="text" id="collisionRecordCost" class="form-control" placeholder="Cost of the repair" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "RepairRecord")
|
||||
}
|
||||
</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())
|
||||
{
|
||||
@@ -33,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
|
||||
@@ -48,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>
|
||||
@@ -57,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)
|
||||
@@ -71,6 +87,7 @@
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var selectedSupplies = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -40,12 +42,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1">Date</th>
|
||||
<th scope="col" class="col-2 col-xl-1">Date</th>
|
||||
<th scope="col" class="col-2">Odometer</th>
|
||||
<th scope="col" class="col-4">Description</th>
|
||||
<th scope="col" class="col-2">Cost</th>
|
||||
<th scope="col" class="col-3 col-xl-4">Description</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>
|
||||
@@ -53,9 +55,9 @@
|
||||
@foreach (CollisionRecord collisionRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditCollisionRecordModal(@collisionRecord.Id)">
|
||||
<td class="col-1">@collisionRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2 col-xl-1">@collisionRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2">@collisionRecord.Mileage</td>
|
||||
<td class="col-4">@collisionRecord.Description</td>
|
||||
<td class="col-3 col-xl-4">@collisionRecord.Description</td>
|
||||
<td class="col-2">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||
</tr>
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
new Chart($("#pie-chart"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ["Planned Maintenance(Service Records)", "Unplanned Maintenance(Repairs)", "Upgrades", "Tax", "Fuel"],
|
||||
labels: ["Service Records", "Repairs", "Upgrades", "Tax", "Fuel"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Expenses by Category",
|
||||
label: "Expenses by Type",
|
||||
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
|
||||
data: [
|
||||
@Model.ServiceRecordSum,
|
||||
@Model.CollisionRecordSum,
|
||||
@Model.UpgradeRecordSum,
|
||||
@Model.TaxRecordSum,
|
||||
@Model.GasRecordSum
|
||||
globalParseFloat('@Model.ServiceRecordSum'),
|
||||
globalParseFloat('@Model.CollisionRecordSum'),
|
||||
globalParseFloat('@Model.UpgradeRecordSum'),
|
||||
globalParseFloat('@Model.TaxRecordSum'),
|
||||
globalParseFloat('@Model.GasRecordSum')
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject IGasHelper gasHelper
|
||||
@model GasRecordViewModelContainer
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var useMPG = config.GetUserConfig(User).UseMPG;
|
||||
var useUKMPG = config.GetUserConfig(User).UseUKMPG;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var useMPG = userConfig.UseMPG;
|
||||
var useUKMPG = userConfig.UseUKMPG;
|
||||
var hideZero = userConfig.HideZero;
|
||||
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">
|
||||
@@ -37,12 +42,12 @@
|
||||
<span class="ms-2 badge bg-success">@($"# of Gas Records: {Model.GasRecords.Count()}")</span>
|
||||
@if (Model.GasRecords.Where(x => x.MilesPerGallon > 0).Any())
|
||||
{
|
||||
<span class="ms-2 badge bg-primary">@($"Average Fuel Economy: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Average(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"Average Fuel Economy: {gasHelper.GetAverageGasMileage(Model.GasRecords, useMPG)}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"Min Fuel Economy: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"Max Fuel Economy: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||
}
|
||||
<span class="ms-2 badge bg-success">@($"Total Fuel Consumed: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
||||
<span class="ms-2 badge bg-success">@($"Total Cost: {Model.GasRecords.Sum(x => x.Cost).ToString("C3")}")</span>
|
||||
<span class="ms-2 badge bg-success">@($"Total Cost: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
|
||||
</div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
@@ -54,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 {
|
||||
@@ -69,14 +76,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<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>
|
||||
@@ -87,8 +94,8 @@
|
||||
<td class="col-2">@gasRecord.Mileage</td>
|
||||
<td class="col-2">@gasRecord.Gallons.ToString("F")</td>
|
||||
<td class="col-4">@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
||||
<td class="col-1">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString("C3"))</td>
|
||||
<td class="col-1">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString("C3"))</td>
|
||||
<td class="col-1">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
|
||||
<td class="col-1">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
@model List<CostForVehicleByMonth>
|
||||
@if (Model.Any())
|
||||
@{
|
||||
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
||||
var sortedByMPG = Model.OrderBy(x => x.Cost).ToList();
|
||||
}
|
||||
@if (Model.Where(x=>x.Cost > 0).Any())
|
||||
{
|
||||
<canvas id="bar-chart"></canvas>
|
||||
<script>
|
||||
@@ -7,11 +11,15 @@
|
||||
function renderChart() {
|
||||
var barGraphLabels = [];
|
||||
var barGraphData = [];
|
||||
//color gradient from high to low
|
||||
var barGraphColors = [];
|
||||
var useDarkMode = getGlobalConfig().useDarkMode;
|
||||
@foreach (CostForVehicleByMonth gasCost in Model)
|
||||
{
|
||||
@:barGraphLabels.push("@gasCost.MonthName");
|
||||
@:barGraphData.push(@gasCost.Cost);
|
||||
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
|
||||
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
||||
@:barGraphColors.push('@barGraphColors[index]');
|
||||
}
|
||||
new Chart($("#bar-chart"), {
|
||||
type: 'bar',
|
||||
@@ -20,14 +28,20 @@
|
||||
datasets: [
|
||||
{
|
||||
label: "Expenses by Month",
|
||||
backgroundColor: ["#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f"],
|
||||
backgroundColor: barGraphColors,
|
||||
data: barGraphData
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
color: useDarkMode ? "#fff" : "#000",
|
||||
text: 'Expenses by Month'
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
labels: {
|
||||
color: useDarkMode ? "#fff" : "#000"
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -56,21 +61,39 @@
|
||||
<label class="form-check-label" for="gasIsMissed">Missed Fuel Up(Skip MPG Calculation)</label>
|
||||
</div>
|
||||
<label for="GasRecordCost">Cost</label>
|
||||
<input type="text" id="gasRecordCost" class="form-control" placeholder="Cost of gas refueled" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group">
|
||||
<input type="text" id="gasRecordCost" class="form-control" placeholder="Cost of gas refueled" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<div class="input-group-text">
|
||||
<select class="form-select form-select-sm" id="gasCostType">
|
||||
<option value="total">Total</option>
|
||||
<option value="unit">Unit</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<input type="text" id="gasRecordCost" class="form-control" placeholder="Cost of gas refueled" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<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())
|
||||
{
|
||||
<div>
|
||||
@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>
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
@model List<CostForVehicleByMonth>
|
||||
@if (Model.Any())
|
||||
@{
|
||||
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
||||
var sortedByMPG = Model.OrderByDescending(x => x.Cost).ToList();
|
||||
}
|
||||
@if (Model.Where(x=>x.Cost > 0).Any())
|
||||
{
|
||||
|
||||
<canvas id="bar-chart-mpg"></canvas>
|
||||
<script>
|
||||
renderChart();
|
||||
function renderChart() {
|
||||
var barGraphLabels = [];
|
||||
var barGraphData = [];
|
||||
var useDarkMode = getGlobalConfig().useDarkMode;
|
||||
var barGraphLabels = [];
|
||||
var barGraphData = [];
|
||||
//color gradient from high to low
|
||||
var barGraphColors = [];
|
||||
var useDarkMode = getGlobalConfig().useDarkMode;
|
||||
@foreach (CostForVehicleByMonth gasCost in Model)
|
||||
{
|
||||
@:barGraphLabels.push("@gasCost.MonthName");
|
||||
@:barGraphData.push(@gasCost.Cost);
|
||||
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
|
||||
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
||||
@:barGraphColors.push('@barGraphColors[index]');
|
||||
}
|
||||
new Chart($("#bar-chart-mpg"), {
|
||||
new Chart($("#bar-chart-mpg"), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: barGraphLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Fuel Mileage by Month",
|
||||
backgroundColor: ["#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f"],
|
||||
backgroundColor: barGraphColors,
|
||||
data: barGraphData
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
color: useDarkMode ? "#fff" : "#000",
|
||||
text: 'Fuel Mileage by Month'
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
labels: {
|
||||
color: useDarkMode ? "#fff" : "#000"
|
||||
}
|
||||
|
||||
@@ -12,11 +12,15 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="noteIsPinned" checked="@Model.Pinned">
|
||||
<label class="form-check-label" for="noteIsPinned">Pinned</label>
|
||||
</div>
|
||||
<label for="noteDescription">Description</label>
|
||||
<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>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-3">Description</th>
|
||||
<th scope="col" class="col-9">Note</th>
|
||||
@@ -27,7 +27,13 @@
|
||||
@foreach (Note note in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)">
|
||||
<td class="col-3">@note.Description</td>
|
||||
@if (note.Pinned)
|
||||
{
|
||||
<td class="col-3"><i class='bi bi-pin-fill me-2'></i>@note.Description</td>
|
||||
} else
|
||||
{
|
||||
<td class="col-3">@note.Description</td>
|
||||
}
|
||||
<td class="col-9 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(note.NoteText, 100)</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
73
Views/Vehicle/_OdometerRecordModal.cshtml
Normal file
73
Views/Vehicle/_OdometerRecordModal.cshtml
Normal file
@@ -0,0 +1,73 @@
|
||||
@model OdometerRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Odometer Record" : "Edit Odometer Record")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddOdometerRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="odometerRecordDate">Date</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="odometerRecordDate" class="form-control" placeholder="Date recorded" value="@Model.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="odometerRecordMileage">Odometer</label>
|
||||
<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)<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())
|
||||
{
|
||||
<div>
|
||||
@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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-danger" onclick="deleteOdometerRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddOdometerRecordModal()">Cancel</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle()">Add New Odometer Record</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle(true)">Edit Odometer Record</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
{
|
||||
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
|
||||
}
|
||||
}
|
||||
function getOdometerRecordModelData() {
|
||||
return { id: @Model.Id}
|
||||
}
|
||||
</script>
|
||||
71
Views/Vehicle/_OdometerRecords.cshtml
Normal file
71
Views/Vehicle/_OdometerRecords.cshtml
Normal file
@@ -0,0 +1,71 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
}
|
||||
@model List<OdometerRecord>
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
<span class="ms-2 badge bg-success">@($"# of Odometer Records: {Model.Count()}")</span>
|
||||
</div>
|
||||
<div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button onclick="showAddOdometerRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Odometer Record</button>
|
||||
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
<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>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button onclick="showAddOdometerRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Odometer Record</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row vehicleDetailTabContainer">
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-2 col-xl-1">Date</th>
|
||||
<th scope="col" class="col-3">Odometer</th>
|
||||
<th scope="col" class="col-7 col-xl-8">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (OdometerRecord odometerRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditOdometerRecordModal(@odometerRecord.Id)">
|
||||
<td class="col-2 col-xl-1">@odometerRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-3">@odometerRecord.Mileage</td>
|
||||
<td class="col-7 col-xl-8 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="odometerRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="odometerRecordModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
49
Views/Vehicle/_PlanRecordItem.cshtml
Normal file
49
Views/Vehicle/_PlanRecordItem.cshtml
Normal file
@@ -0,0 +1,49 @@
|
||||
@model PlanRecord
|
||||
<div class="taskCard @(Model.Progress == PlanProgress.Done ? "nodrag" : "") text-dark user-select-none mt-2 mb-2" draggable="@(Model.Progress == PlanProgress.Done ? "false" : "true")" ondragstart="dragStart(event, @Model.Id)" onclick="@(Model.Progress == PlanProgress.Done ? $"deletePlanRecord({Model.Id})" : $"showEditPlanRecordModal({Model.Id})")">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-8 text-truncate">
|
||||
@if (Model.Progress == PlanProgress.Done)
|
||||
{
|
||||
<span class="taskCard-title text-truncate"><s>@Model.Description</s></span>
|
||||
} else
|
||||
{
|
||||
<span class="taskCard-title text-truncate">@Model.Description</span>
|
||||
}
|
||||
</div>
|
||||
<div class="col-12 col-lg-4 d-flex align-items-center">
|
||||
<span class="text-truncate">@Model.Cost.ToString("C2")</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 col-md-1">
|
||||
@if (Model.ImportMode == ImportMode.ServiceRecord)
|
||||
{
|
||||
<i class="bi bi-card-checklist"></i>
|
||||
}
|
||||
else if (Model.ImportMode == ImportMode.UpgradeRecord)
|
||||
{
|
||||
<i class="bi bi-wrench-adjustable"></i>
|
||||
}
|
||||
else if (Model.ImportMode == ImportMode.RepairRecord)
|
||||
{
|
||||
<i class="bi bi-exclamation-octagon"></i>
|
||||
}
|
||||
</div>
|
||||
<div class="col-6 col-md-1">
|
||||
@if (Model.Priority == PlanPriority.Critical)
|
||||
{
|
||||
<i class="bi bi-fire"></i>
|
||||
}
|
||||
else if (Model.Priority == PlanPriority.Normal)
|
||||
{
|
||||
<i class="bi bi-water"></i>
|
||||
}
|
||||
else if (Model.Priority == PlanPriority.Low)
|
||||
{
|
||||
<i class="bi bi-snow"></i>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
101
Views/Vehicle/_PlanRecordModal.cshtml
Normal file
101
Views/Vehicle/_PlanRecordModal.cshtml
Normal file
@@ -0,0 +1,101 @@
|
||||
@model PlanRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Plan Record" : "Edit Plan Record")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddPlanRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="planRecordDescription">Description</label>
|
||||
<input type="text" id="planRecordDescription" class="form-control" placeholder="Describe the Plan" value="@Model.Description">
|
||||
<label for="planRecordCost">Cost</label>
|
||||
<input type="text" id="planRecordCost" class="form-control" placeholder="Cost of the Plan" value="@Model.Cost">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "PlanRecord")
|
||||
}
|
||||
<label for="planRecordType">Type</label>
|
||||
<select class="form-select" id="planRecordType">
|
||||
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>Service</!option>
|
||||
<!option value="RepairRecord" @(Model.ImportMode == ImportMode.RepairRecord ? "selected" : "")>Repair</!option>
|
||||
<!option value="UpgradeRecord" @(Model.ImportMode == ImportMode.UpgradeRecord ? "selected" : "")>Upgrade</!option>
|
||||
</select>
|
||||
<label for="planRecordPriority">Priority</label>
|
||||
<select class="form-select" id="planRecordPriority">
|
||||
<!option value="Critical" @(Model.Priority == PlanPriority.Critical ? "selected" : "")>Critical</!option>
|
||||
<!option value="Normal" @(Model.Priority == PlanPriority.Normal || isNew ? "selected" : "")>Normal</!option>
|
||||
<!option value="Low" @(Model.Priority == PlanPriority.Low ? "selected" : "")>Low</!option>
|
||||
</select>
|
||||
<label for="planRecordProgress">Current Stage</label>
|
||||
<select class="form-select" id="planRecordProgress">
|
||||
<!option value="Backlog" @(Model.Progress == PlanProgress.Backlog ||isNew ? "selected" : "")>Planned</!option>
|
||||
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>Doing</!option>
|
||||
<!option value="Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>Testing</!option>
|
||||
</select>
|
||||
@if (!isNew)
|
||||
{
|
||||
<label>Date Created: @Model.DateCreated</label>
|
||||
<label>Last Modified: @Model.DateModified</label>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<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())
|
||||
{
|
||||
<div>
|
||||
@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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-danger" onclick="deletePlanRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">Cancel</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle()">Add New Plan Record</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle(true)">Edit Plan Record</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var selectedSupplies = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
{
|
||||
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
|
||||
}
|
||||
}
|
||||
function getPlanRecordModelData() {
|
||||
return {
|
||||
id: @Model.Id,
|
||||
dateCreated: decodeHTMLEntities('@(Model.DateCreated)')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
100
Views/Vehicle/_PlanRecords.cshtml
Normal file
100
Views/Vehicle/_PlanRecords.cshtml
Normal file
@@ -0,0 +1,100 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
var backLogItems = Model.Where(x => x.Progress == PlanProgress.Backlog).OrderBy(x=>x.Priority);
|
||||
var inProgressItems = Model.Where(x => x.Progress == PlanProgress.InProgress).OrderBy(x => x.Priority);
|
||||
var testingItems = Model.Where(x => x.Progress == PlanProgress.Testing).OrderBy(x => x.Priority);
|
||||
var doneItems = Model.Where(x => x.Progress == PlanProgress.Done).OrderBy(x => x.Priority);
|
||||
}
|
||||
@model List<PlanRecord>
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
<span class="ms-2 badge bg-success">@($"# of Plan Records: {Model.Count()}")</span>
|
||||
</div>
|
||||
<div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button onclick="showAddPlanRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Plan Record</button>
|
||||
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('PlanRecord')">Import via CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('PlanRecord')">Export to CSV</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button onclick="showAddPlanRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Plan Record</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row vehicleDetailTabContainer fixed">
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row swimlane">
|
||||
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||
<span class="display-7">Planned</span>
|
||||
</div>
|
||||
</div>
|
||||
@foreach (PlanRecord planRecord in backLogItems)
|
||||
{
|
||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||
}
|
||||
</div>
|
||||
<div class="col-3 d-flex flex-column swimlane mid" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||
<span class="display-7">Doing</span>
|
||||
</div>
|
||||
</div>
|
||||
@foreach (PlanRecord planRecord in inProgressItems)
|
||||
{
|
||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||
}
|
||||
</div>
|
||||
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Testing')">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||
<span class="display-7">Testing</span>
|
||||
</div>
|
||||
</div>
|
||||
@foreach (PlanRecord planRecord in testingItems)
|
||||
{
|
||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||
}
|
||||
</div>
|
||||
<div class="col-3 d-flex flex-column swimlane end" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
|
||||
<span class="display-7">Done</span>
|
||||
</div>
|
||||
</div>
|
||||
@foreach (PlanRecord planRecord in doneItems)
|
||||
{
|
||||
@await Html.PartialAsync("_PlanRecordItem", planRecord)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="planRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="planRecordModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,8 +39,41 @@
|
||||
</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">
|
||||
<label class="form-check-label" for="reminderIsRecurring">Is Recurring</label>
|
||||
</div>
|
||||
<label for="reminderRecurringMileage">Odometer</label>
|
||||
<select class="form-select" id="reminderRecurringMileage" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<!option value="FiftyMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyMiles ? "selected" : "")>50 mi. / Km</!option>
|
||||
<!option value="OneHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredMiles ? "selected" : "")>100 mi. / Km</!option>
|
||||
<!option value="FiveHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiveHundredMiles ? "selected" : "")>500 mi. / Km</!option>
|
||||
<!option value="OneThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneThousandMiles ? "selected" : "")>1000 mi. / Km</!option>
|
||||
<!option value="ThreeThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.ThreeThousandMiles ? "selected" : "")>3000 mi. / Km</!option>
|
||||
<!option value="FourThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FourThousandMiles ? "selected" : "")>4000 mi. / Km</!option>
|
||||
<!option value="FiveThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiveThousandMiles || isNew ? "selected" : "")>5000 mi. / Km</!option>
|
||||
<!option value="SevenThousandFiveHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.SevenThousandFiveHundredMiles ? "selected" : "")>7500 mi. / Km</!option>
|
||||
<!option value="TenThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.TenThousandMiles ? "selected" : "")>10000 mi. / Km</!option>
|
||||
<!option value="FifteenThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FifteenThousandMiles ? "selected" : "")>15000 mi. / Km</!option>
|
||||
<!option value="TwentyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.TwentyThousandMiles ? "selected" : "")>20000 mi. / Km</!option>
|
||||
<!option value="ThirtyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.ThirtyThousandMiles ? "selected" : "")>30000 mi. / Km</!option>
|
||||
<!option value="FortyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FortyThousandMiles ? "selected" : "")>40000 mi. / Km</!option>
|
||||
<!option value="FiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyThousandMiles ? "selected" : "")>50000 mi. / Km</!option>
|
||||
<!option value="SixtyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.SixtyThousandMiles ? "selected" : "")>60000 mi. / Km</!option>
|
||||
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
|
||||
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
|
||||
</select>
|
||||
<label for="reminderRecurringMonth">Month</label>
|
||||
<select class="form-select" id="reminderRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>3 Months</!option>
|
||||
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>6 Months</!option>
|
||||
<!option value="OneYear" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneYear ? "selected" : "")>1 Year</!option>
|
||||
<!option value="TwoYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.TwoYears ? "selected" : "")>2 Years</!option>
|
||||
<!option value="ThreeYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeYears ? "selected" : "")>3 Years</!option>
|
||||
<!option value="FiveYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>5 Years</!option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
@model List<ReminderRecordViewModel>
|
||||
@{
|
||||
var hasRefresh = Model.Where(x => (x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue) && x.IsRecurring).Any();
|
||||
}
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
@@ -21,12 +24,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1">Urgency</th>
|
||||
<th scope="col" class="col-2">Metric</th>
|
||||
<th scope="col" class="col-5">Description</th>
|
||||
<th scope="col" class="@(hasRefresh ? "col-4" : "col-5")">Description</th>
|
||||
<th scope="col" class="col-3">Notes</th>
|
||||
@if (hasRefresh)
|
||||
{
|
||||
<th scope="col" class="col-1">Done</th>
|
||||
}
|
||||
<th scope="col" class="col-1">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -62,8 +69,17 @@
|
||||
{
|
||||
<td class="col-2">@reminderRecord.Metric</td>
|
||||
}
|
||||
<td class="col-5">@reminderRecord.Description</td>
|
||||
<td class="@(hasRefresh ? "col-4" : "col-5")">@reminderRecord.Description</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
|
||||
@if (hasRefresh)
|
||||
{
|
||||
<td class="col-1 text-truncate">
|
||||
@if((reminderRecord.Urgency == ReminderUrgency.VeryUrgent || reminderRecord.Urgency == ReminderUrgency.PastDue) && reminderRecord.IsRecurring)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" onclick="markDoneReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-check-lg"></i></button>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
<td class="col-1 text-truncate">
|
||||
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
|
||||
</td>
|
||||
|
||||
@@ -1,85 +1,92 @@
|
||||
@model ReportViewModel
|
||||
<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="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>
|
||||
</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-12 d-flex justify-content-center reportsCheckBoxContainer">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" onChange="updateCheck(this)" 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(this)" 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(this)" 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(this)" 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(this)" type="checkbox" id="taxExpenseCheck" value="5" checked>
|
||||
<label class="form-check-label" for="taxExpenseCheck">Tax</label>
|
||||
<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="gasCostByMonthReportContent">
|
||||
@await Html.PartialAsync("_GasCostByMonthReport", Model.CostForVehicleByMonth)
|
||||
<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="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>
|
||||
|
||||
@@ -23,9 +23,13 @@
|
||||
<input type="text" id="serviceRecordDescription" class="form-control" placeholder="Description of item(s) serviced(i.e. Oil Change)" value="@Model.Description">
|
||||
<label for="serviceRecordCost">Cost</label>
|
||||
<input type="text" id="serviceRecordCost" class="form-control" placeholder="Cost of the service" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "ServiceRecord")
|
||||
}
|
||||
</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())
|
||||
{
|
||||
@@ -33,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
|
||||
@@ -48,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>
|
||||
@@ -57,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)
|
||||
@@ -71,6 +87,7 @@
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var selectedSupplies = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -40,12 +42,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1">Date</th>
|
||||
<th scope="col" class="col-2 col-xl-1">Date</th>
|
||||
<th scope="col" class="col-2">Odometer</th>
|
||||
<th scope="col" class="col-4">Description</th>
|
||||
<th scope="col" class="col-2">Cost</th>
|
||||
<th scope="col" class="col-3 col-xl-4">Description</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>
|
||||
@@ -53,9 +55,9 @@
|
||||
@foreach (ServiceRecord serviceRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditServiceRecordModal(@serviceRecord.Id)">
|
||||
<td class="col-1">@serviceRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2 col-xl-1">@serviceRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2">@serviceRecord.Mileage</td>
|
||||
<td class="col-4">@serviceRecord.Description</td>
|
||||
<td class="col-3 col-xl-4">@serviceRecord.Description</td>
|
||||
<td class="col-2">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
|
||||
</tr>
|
||||
|
||||
87
Views/Vehicle/_SupplyRecordModal.cshtml
Normal file
87
Views/Vehicle/_SupplyRecordModal.cshtml
Normal file
@@ -0,0 +1,87 @@
|
||||
@model SupplyRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Supply Record" : "Edit Supply Record")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddSupplyRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="supplyRecordDate">Date</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="supplyRecordDate" class="form-control" placeholder="Date purchased" value="@Model.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="supplyRecordPartNumber">Part Number</label>
|
||||
<input type="text" id="supplyRecordPartNumber" class="form-control" placeholder="Part #/Model #/SKU #" value="@(isNew ? "" : Model.PartNumber)">
|
||||
<label for="supplyRecordDescription">Description</label>
|
||||
<input type="text" id="supplyRecordDescription" class="form-control" placeholder="Description of the Part/Supplies" value="@Model.Description">
|
||||
<label for="supplyRecordSupplier">Supplier/Vendor</label>
|
||||
<input type="text" id="supplyRecordSupplier" class="form-control" placeholder="Part Supplier" value="@Model.PartSupplier">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordQuantity">Quantity</label>
|
||||
<input type="text" id="supplyRecordQuantity" class="form-control" placeholder="Quantity" value="@(isNew ? "1" : Model.Quantity)">
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordCost">Cost</label>
|
||||
<input type="text" id="supplyRecordCost" class="form-control" placeholder="Cost" value="@(isNew ? "" : Model.Cost)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<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())
|
||||
{
|
||||
<div>
|
||||
@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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-danger" onclick="deleteSupplyRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddSupplyRecordModal()">Cancel</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle()">Add New Supply Record</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle(true)">Edit Supply Record</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
{
|
||||
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
|
||||
}
|
||||
}
|
||||
function getSupplyRecordModelData() {
|
||||
return { id: @Model.Id}
|
||||
}
|
||||
</script>
|
||||
80
Views/Vehicle/_SupplyRecords.cshtml
Normal file
80
Views/Vehicle/_SupplyRecords.cshtml
Normal file
@@ -0,0 +1,80 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
}
|
||||
@model List<SupplyRecord>
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
<span class="ms-2 badge bg-success">@($"# of Supply Records: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"Total: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
</div>
|
||||
<div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button onclick="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Supply Record</button>
|
||||
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
<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>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button onclick="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Supply Record</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row vehicleDetailTabContainer">
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-2 col-xl-1">Date</th>
|
||||
<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" 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>
|
||||
<tbody>
|
||||
@foreach (SupplyRecord supplyRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditSupplyRecordModal(@supplyRecord.Id)">
|
||||
<td class="col-2 col-xl-1">@supplyRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2">@supplyRecord.PartNumber</td>
|
||||
<td class="col-2">@supplyRecord.PartSupplier</td>
|
||||
<td class="col-2 col-xl-3">@supplyRecord.Description</td>
|
||||
<td class="col-1">@supplyRecord.Quantity</td>
|
||||
<td class="col-1">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-2 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="supplyRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="supplyRecordModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
91
Views/Vehicle/_SupplyStore.cshtml
Normal file
91
Views/Vehicle/_SupplyStore.cshtml
Normal file
@@ -0,0 +1,91 @@
|
||||
@model string
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<a onclick="showSuppliesModal()" class="btn btn-link">Choose Supplies</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
resetSuppliesModal();
|
||||
function GetCaller() {
|
||||
return { tab: '@Model' };
|
||||
}
|
||||
function resetSuppliesModal() {
|
||||
$("#inputSuppliesModalContent").html("");
|
||||
}
|
||||
function selectSupplies() {
|
||||
var selectedSupplyResult = getSuppliesAndQuantity();
|
||||
var caller = GetCaller().tab;
|
||||
switch (caller) {
|
||||
case "ServiceRecord":
|
||||
$('#serviceRecordCost').val(selectedSupplyResult.totalSum);
|
||||
break;
|
||||
case "RepairRecord":
|
||||
$('#collisionRecordCost').val(selectedSupplyResult.totalSum);
|
||||
break;
|
||||
case "UpgradeRecord":
|
||||
$('#upgradeRecordCost').val(selectedSupplyResult.totalSum);
|
||||
break;
|
||||
case "PlanRecord":
|
||||
$('#planRecordCost').val(selectedSupplyResult.totalSum);
|
||||
break;
|
||||
}
|
||||
selectedSupplies = getSuppliesAndQuantity().selectedSupplies;
|
||||
hideSuppliesModal();
|
||||
}
|
||||
function hideParentModal(){
|
||||
var caller = GetCaller().tab;
|
||||
switch (caller) {
|
||||
case "ServiceRecord":
|
||||
$('#serviceRecordModal').modal('hide');
|
||||
break;
|
||||
case "RepairRecord":
|
||||
$('#collisionRecordModal').modal('hide');
|
||||
break;
|
||||
case "UpgradeRecord":
|
||||
$('#upgradeRecordModal').modal('hide');
|
||||
break;
|
||||
case "PlanRecord":
|
||||
$('#planRecordModal').modal('hide');
|
||||
break;
|
||||
}
|
||||
}
|
||||
function showParentModal() {
|
||||
var caller = GetCaller().tab;
|
||||
switch (caller) {
|
||||
case "ServiceRecord":
|
||||
$('#serviceRecordModal').modal('show');
|
||||
break;
|
||||
case "RepairRecord":
|
||||
$('#collisionRecordModal').modal('show');
|
||||
break;
|
||||
case "UpgradeRecord":
|
||||
$('#upgradeRecordModal').modal('show');
|
||||
break;
|
||||
case "PlanRecord":
|
||||
$('#planRecordModal').modal('show');
|
||||
break;
|
||||
}
|
||||
}
|
||||
function showSuppliesModal() {
|
||||
if ($("#inputSuppliesModalContent").html() == "") {
|
||||
getSupplies();
|
||||
} else {
|
||||
hideParentModal();
|
||||
$('#inputSuppliesModal').modal('show');
|
||||
}
|
||||
}
|
||||
function getSupplies() {
|
||||
var vehicleId = GetVehicleId().vehicleId;
|
||||
$.get(`/Vehicle/GetSupplyRecordsForRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
||||
if (data) {
|
||||
hideParentModal();
|
||||
$("#inputSuppliesModalContent").html(data);
|
||||
$('#inputSuppliesModal').modal('show');
|
||||
}
|
||||
})
|
||||
}
|
||||
function hideSuppliesModal() {
|
||||
$('#inputSuppliesModal').modal('hide');
|
||||
showParentModal();
|
||||
}
|
||||
</script>
|
||||
127
Views/Vehicle/_SupplyUsage.cshtml
Normal file
127
Views/Vehicle/_SupplyUsage.cshtml
Normal file
@@ -0,0 +1,127 @@
|
||||
@model List<SupplyRecord>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Select Supplies</h5>
|
||||
<button type="button" class="btn-close" onclick="hideSuppliesModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
Supplies are requisitioned immediately after the record is created and cannot be modified.
|
||||
If you have incorrectly entered the amount you needed you will need to correct it in the Supplies tab.
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1"></th>
|
||||
<th scope="col" class="col-2">Quantity.</th>
|
||||
<th scope="col" class="col-2">In Stock</th>
|
||||
<th scope="col" class="col-5">Description</th>
|
||||
<th scope="col" class="col-2">Unit Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (SupplyRecord supplyRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" id="supplyRows">
|
||||
<td class="col-1"><input class="form-check-input" type="checkbox" onchange="toggleQuantityFieldDisabled(this)" value="@supplyRecord.Id"></td>
|
||||
<td class="col-2"><input type="text" disabled onchange="recalculateTotal()" class="form-control"></td>
|
||||
<td class="col-2 supplyquantity">@supplyRecord.Quantity</td>
|
||||
<td class="col-5">@supplyRecord.Description</td>
|
||||
<td class="col-2 supplyprice">@((supplyRecord.Quantity > 0 ? supplyRecord.Cost / supplyRecord.Quantity : 0).ToString("F"))</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="text-center">
|
||||
<h4>No supplies with quantities greater than 0 is found.</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span id="supplySumLabel" style="margin-right:auto;">Total: 0.00</span>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideSuppliesModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" disabled id="selectSuppliesButton" onclick="selectSupplies()">Select</button>
|
||||
</div>
|
||||
<script>
|
||||
function recalculateTotal() {
|
||||
setDebounce(getSuppliesAndQuantity);
|
||||
}
|
||||
function toggleQuantityFieldDisabled(e) {
|
||||
var textField = getTextFieldFromCheckBox(e);
|
||||
var isChecked = $(e).is(":checked");
|
||||
textField.attr('disabled', !isChecked);
|
||||
if (!isChecked) {
|
||||
textField.removeClass("is-invalid");
|
||||
}
|
||||
recalculateTotal();
|
||||
}
|
||||
function getTextFieldFromCheckBox(elem) {
|
||||
var textField = $(elem.parentElement.parentElement).find('.col-2 > input[type=text]')[0];
|
||||
return $(textField);
|
||||
}
|
||||
function getInStockFieldFromCheckBox(elem) {
|
||||
var textField = $(elem.parentElement.parentElement).find('.col-2.supplyquantity')[0];
|
||||
return $(textField);
|
||||
}
|
||||
function getPriceFieldFromCheckBox(elem) {
|
||||
var textField = $(elem.parentElement.parentElement).find('.col-2.supplyprice')[0];
|
||||
return $(textField);
|
||||
}
|
||||
function getSuppliesAndQuantity() {
|
||||
var totalSum = 0;
|
||||
var hasError = false;
|
||||
var selectedSupplies = $("#supplyRows :checked").map(function () {
|
||||
var textField = getTextFieldFromCheckBox(this);
|
||||
var inStock = getInStockFieldFromCheckBox(this);
|
||||
var priceField = getPriceFieldFromCheckBox(this);
|
||||
var requestedQuantity = globalParseFloat(textField.val());
|
||||
var inStockQuantity = globalParseFloat(inStock.text());
|
||||
var unitPrice = globalParseFloat(priceField.text());
|
||||
//validation
|
||||
if (isNaN(requestedQuantity) || requestedQuantity > inStockQuantity) {
|
||||
textField.addClass("is-invalid");
|
||||
hasError = true;
|
||||
} else {
|
||||
textField.removeClass("is-invalid");
|
||||
}
|
||||
//calculate sum.
|
||||
var sum = requestedQuantity * unitPrice;
|
||||
totalSum += sum;
|
||||
return {
|
||||
supplyId: this.value,
|
||||
quantity: textField.val()
|
||||
};
|
||||
});
|
||||
if (isNaN(totalSum) || hasError) {
|
||||
$("#supplySumLabel").text(`Total: 0.00`);
|
||||
} else {
|
||||
totalSum = totalSum.toFixed(2);
|
||||
var parsedFloat = globalFloatToString(totalSum);
|
||||
$("#supplySumLabel").text(`Total: ${parsedFloat}`);
|
||||
}
|
||||
$("#selectSuppliesButton").attr('disabled', (hasError || totalSum == 0));
|
||||
if (!hasError) {
|
||||
return {
|
||||
totalSum: totalSum,
|
||||
selectedSupplies: selectedSupplies.toArray()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
totalSum: 0,
|
||||
selectedSupplies: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -23,14 +23,29 @@
|
||||
<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">
|
||||
<label class="form-check-label" for="taxIsRecurring">Is Recurring</label>
|
||||
</div>
|
||||
<label for="taxRecurringMonth">Month</label>
|
||||
<select class="form-select" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<!option value="OneMonth" @(Model.RecurringInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>1 Month</!option>
|
||||
<!option value="ThreeMonths" @(Model.RecurringInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>3 Months</!option>
|
||||
<!option value="SixMonths" @(Model.RecurringInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>6 Months</!option>
|
||||
<!option value="OneYear" @(Model.RecurringInterval == ReminderMonthInterval.OneYear ? "selected" : "")>1 Year</!option>
|
||||
<!option value="TwoYears" @(Model.RecurringInterval == ReminderMonthInterval.TwoYears ? "selected" : "")>2 Years</!option>
|
||||
<!option value="ThreeYears" @(Model.RecurringInterval == ReminderMonthInterval.ThreeYears ? "selected" : "")>3 Years</!option>
|
||||
<!option value="FiveYears" @(Model.RecurringInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>5 Years</!option>
|
||||
</select>
|
||||
@if (Model.Files.Any())
|
||||
{
|
||||
<div>
|
||||
@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
|
||||
@@ -46,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>
|
||||
}
|
||||
@@ -40,11 +42,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1">Date</th>
|
||||
<th scope="col" class="col-6">Description</th>
|
||||
<th scope="col" class="col-2">Cost</th>
|
||||
<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" onclick="toggleSort('tax-tab-pane', this)" style="cursor:pointer;">Cost</th>
|
||||
<th scope="col" class="col-3">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -52,8 +54,8 @@
|
||||
@foreach (TaxRecord taxRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditTaxRecordModal(@taxRecord.Id)">
|
||||
<td class="col-1">@taxRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-6">@taxRecord.Description</td>
|
||||
<td class="col-3 col-xl-1">@taxRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-4 col-xl-6">@taxRecord.Description</td>
|
||||
<td class="col-2">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(taxRecord.Notes)</td>
|
||||
</tr>
|
||||
|
||||
@@ -23,9 +23,13 @@
|
||||
<input type="text" id="upgradeRecordDescription" class="form-control" placeholder="Description of item(s) upgraded/modded" value="@Model.Description">
|
||||
<label for="upgradeRecordCost">Cost</label>
|
||||
<input type="text" id="upgradeRecordCost" class="form-control" placeholder="Cost of the upgrade/mods" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "UpgradeRecord")
|
||||
}
|
||||
</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())
|
||||
{
|
||||
@@ -33,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
|
||||
@@ -48,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>
|
||||
@@ -57,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)
|
||||
@@ -71,6 +87,7 @@
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var selectedSupplies = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -40,12 +42,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1">Date</th>
|
||||
<th scope="col" class="col-2 col-xl-1">Date</th>
|
||||
<th scope="col" class="col-2">Odometer</th>
|
||||
<th scope="col" class="col-4">Description</th>
|
||||
<th scope="col" class="col-2">Cost</th>
|
||||
<th scope="col" class="col-3 col-xl-4">Description</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>
|
||||
@@ -53,9 +55,9 @@
|
||||
@foreach (UpgradeRecord upgradeRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditUpgradeRecordModal(@upgradeRecord.Id)">
|
||||
<td class="col-1">@upgradeRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2 col-xl-1">@upgradeRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-2">@upgradeRecord.Mileage</td>
|
||||
<td class="col-4">@upgradeRecord.Description</td>
|
||||
<td class="col-3 col-xl-4">@upgradeRecord.Description</td>
|
||||
<td class="col-2">@((hideZero && upgradeRecord.Cost == default) ? "---" : upgradeRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a type="button" class="btn btn-link" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
||||
<a type="button" class="btn btn-link text-truncate uploadedFileName" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
|
||||
|
||||
@@ -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
|
||||
@@ -56,7 +58,7 @@
|
||||
<div class="col-6">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">Last Reported Odometer Reading: @Model.Odometer</li>
|
||||
<li class="list-group-item">Average Fuel Economy: @($"{Model.MPG.ToString("F")} {fuelEconomyUnit}")</li>
|
||||
<li class="list-group-item">Average Fuel Economy: @($"{Model.MPG} {fuelEconomyUnit}")</li>
|
||||
<li class="list-group-item">Total Spent(excl. fuel): @Model.TotalCost.ToString("C")</li>
|
||||
<li class="list-group-item">Total Spent on Fuel: @Model.TotalGasCost.ToString("C")</li>
|
||||
</ul>
|
||||
@@ -68,8 +70,8 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-2">Type</th>
|
||||
<th scope="col" class="col-1">Date</th>
|
||||
<th scope="col" class="col-2 servicehistorytype">Type</th>
|
||||
<th scope="col" class="col-1 servicehistorydate">Date</th>
|
||||
<th scope="col" class="col-1">Odometer</th>
|
||||
<th scope="col" class="col-3">Description</th>
|
||||
<th scope="col" class="col-1">Cost</th>
|
||||
@@ -80,7 +82,7 @@
|
||||
@foreach (GenericReportModel reportData in Model.VehicleHistory)
|
||||
{
|
||||
<tr class="d-flex">
|
||||
<td class="col-2">
|
||||
<td class="col-2 servicehistorytype">
|
||||
@if(reportData.DataType == ImportMode.ServiceRecord)
|
||||
{
|
||||
<span><i class="bi bi-card-checklist me-2"></i>Service</span>
|
||||
@@ -95,7 +97,7 @@
|
||||
<span><i class="bi bi-currency-dollar me-2"></i>Tax</span>
|
||||
}
|
||||
</td>
|
||||
<td class="col-1">@reportData.Date.ToShortDateString()</td>
|
||||
<td class="col-1 servicehistorydate">@reportData.Date.ToShortDateString()</td>
|
||||
<td class="col-1">@(reportData.Odometer == default ? "---" : reportData.Odometer.ToString("N0"))</td>
|
||||
<td class="col-3">@reportData.Description</td>
|
||||
<td class="col-1">@((hideZero && reportData.Cost == default) ? "---" : reportData.Cost.ToString("C"))</td>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -12,7 +12,13 @@
|
||||
"UseDescending": false,
|
||||
"EnableAuth": false,
|
||||
"HideZero": false,
|
||||
"EnableAutoReminderRefresh": false,
|
||||
"EnableAutoOdometerInsert": false,
|
||||
"UseUKMPG": false,
|
||||
"UseThreeDecimalGasCost": true,
|
||||
"UseMarkDownOnSavedNotes": false,
|
||||
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
|
||||
"DefaultTab": 8,
|
||||
"UserNameHash": "",
|
||||
"UserPasswordHash": ""
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user