Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c2715840e | ||
|
|
dfbd90aaf4 | ||
|
|
2b3a4ce8ce | ||
|
|
135292f2b4 | ||
|
|
499c697b0b | ||
|
|
fb74f01609 | ||
|
|
6ab2216742 | ||
|
|
7c97aa70a0 | ||
|
|
ff255e8293 | ||
|
|
57dc976a9f | ||
|
|
13361241e8 | ||
|
|
332af9a04a | ||
|
|
961c60f936 | ||
|
|
34483886be | ||
|
|
548989f9ae | ||
|
|
83a59aff37 | ||
|
|
60f28b16e7 | ||
|
|
9cb8bbd5b7 | ||
|
|
f7c95e9b36 | ||
|
|
cd37eedd15 | ||
|
|
7d11c4003f | ||
|
|
7d9b67a04d | ||
|
|
aba178a324 | ||
|
|
4ecdaf5225 | ||
|
|
c0bc9f7a14 | ||
|
|
094d96d880 | ||
|
|
a8fad6f160 | ||
|
|
6befd5ca76 | ||
|
|
046812ff27 | ||
|
|
db1778a749 | ||
|
|
f27fa1625c | ||
|
|
39a9d5f95b | ||
|
|
234d7163fc | ||
|
|
6f64cc9996 | ||
|
|
58e3b49bdf | ||
|
|
ca2dc1e1e0 | ||
|
|
c65dca4c58 | ||
|
|
82c2351b00 | ||
|
|
f31ca163e5 | ||
|
|
351db5de95 | ||
|
|
41164516f7 | ||
|
|
29a3c815fc | ||
|
|
1768063e57 | ||
|
|
35ce5603e5 | ||
|
|
19e38f9885 | ||
|
|
c55528b8a0 | ||
|
|
1c00f31312 | ||
|
|
8e66530544 | ||
|
|
7bdd3d902b | ||
|
|
abb44608fe | ||
|
|
0b1e3f4be8 | ||
|
|
156eb8ec27 | ||
|
|
472dcac590 | ||
|
|
b4d86f415c | ||
|
|
812e768f93 | ||
|
|
a08d4798e1 | ||
|
|
7bc08c9950 | ||
|
|
20107e3f05 | ||
|
|
ed678c36a5 | ||
|
|
f7574a9a60 | ||
|
|
e506b543e6 | ||
|
|
019a549b64 | ||
|
|
8dbc883a8c | ||
|
|
5fd918115c | ||
|
|
a11f8377fe | ||
|
|
7be19f8603 | ||
|
|
5407ebecb1 | ||
|
|
b75a1b4e6c | ||
|
|
1c28039aaf | ||
|
|
3a1836ef84 | ||
|
|
c747889f85 | ||
|
|
338a8426b2 | ||
|
|
4b1701d158 | ||
|
|
5d64a9a422 | ||
|
|
c2315db127 | ||
|
|
e40129f701 | ||
|
|
d198ad9a6b | ||
|
|
abffc5ab60 | ||
|
|
24ff9db32d | ||
|
|
07fa560344 | ||
|
|
28615d9038 | ||
|
|
d8f53e4b65 | ||
|
|
1f0cef4161 | ||
|
|
af5d72e0d0 | ||
|
|
1397e92709 | ||
|
|
a49121b1d9 | ||
|
|
ca65a6fb71 | ||
|
|
a54857c6bf | ||
|
|
2eadd05289 | ||
|
|
6eafa5e036 | ||
|
|
40f01ad100 | ||
|
|
161b36325e | ||
|
|
f9a6ddd513 | ||
|
|
e85110f8b6 | ||
|
|
76d3dba574 | ||
|
|
178b50a033 | ||
|
|
fb45aefde3 | ||
|
|
05bbc8a95b | ||
|
|
f123299dd6 | ||
|
|
7174413e2a | ||
|
|
d816f73598 | ||
|
|
b249ccdd6c | ||
|
|
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 | ||
|
|
dbb139dfad |
1
.env
1
.env
@@ -6,3 +6,4 @@ MailConfig__UseSSL="false"
|
||||
MailConfig__Port=587
|
||||
MailConfig__Username=""
|
||||
MailConfig__Password=""
|
||||
LOGGING__LOGLEVEL__DEFAULT=Error
|
||||
51
.github/workflows/build-and-push-image.yml
vendored
Normal file
51
.github/workflows/build-and-push-image.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Build and Push Image to Dockerhub and GHCR
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
release:
|
||||
types: ["published"]
|
||||
|
||||
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: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: "hargata"
|
||||
password: "${{ secrets.GHCR_PAT }}"
|
||||
|
||||
- name: Docker Metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
context: workflow
|
||||
images: |
|
||||
hargata/lubelogger
|
||||
ghcr.io/hargata/lubelogger
|
||||
tags: |
|
||||
type=edge,branch=main
|
||||
type=ref,event=tag
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
31
.github/workflows/docker-image.yml
vendored
31
.github/workflows/docker-image.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Docker Image CI
|
||||
|
||||
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 GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: "hargata"
|
||||
password: "${{ secrets.GHCR_PAT }}"
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/hargata/lubelogger:latest
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ data/cartracker.db
|
||||
wwwroot/documents/
|
||||
wwwroot/temp/
|
||||
wwwroot/imports/
|
||||
wwwroot/translations/
|
||||
|
||||
@@ -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,6 +259,102 @@ 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)
|
||||
@@ -115,11 +368,64 @@ namespace CarCareTracker.Controllers
|
||||
FuelConsumed = x.Gallons.ToString(),
|
||||
FuelEconomy = x.MilesPerGallon.ToString(),
|
||||
IsFillToFull = x.IsFillToFull.ToString(),
|
||||
MissedFuelUp = x.MissedFuelUp.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)
|
||||
@@ -129,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>();
|
||||
@@ -152,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");
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,26 @@ namespace CarCareTracker.Controllers
|
||||
return Json(fileName);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult HandleTranslationFileUpload(IFormFile file)
|
||||
{
|
||||
var originalFileName = Path.GetFileNameWithoutExtension(file.FileName);
|
||||
if (originalFileName == "en_US")
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." });
|
||||
}
|
||||
var fileName = UploadFile(file);
|
||||
//move file from temp to translation folder.
|
||||
var uploadedFilePath = _fileHelper.MoveFileFromTemp(fileName, "translations/");
|
||||
//rename uploaded file so that it preserves original name.
|
||||
if (!string.IsNullOrWhiteSpace(uploadedFilePath))
|
||||
{
|
||||
var result = _fileHelper.RenameFile(uploadedFilePath, originalFileName);
|
||||
return Json(new OperationResponse { Success = result, Message = string.Empty });
|
||||
}
|
||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult HandleMultipleFileUpload(List<IFormFile> file)
|
||||
{
|
||||
@@ -44,7 +64,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return Json(uploadedFiles);
|
||||
}
|
||||
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult DeleteFiles(string fileLocation)
|
||||
{
|
||||
|
||||
@@ -15,17 +15,20 @@ namespace CarCareTracker.Controllers
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
private readonly IVehicleDataAccess _dataAccess;
|
||||
private readonly IUserLogic _userLogic;
|
||||
private readonly IFileHelper _fileHelper;
|
||||
private readonly IConfigHelper _config;
|
||||
|
||||
public HomeController(ILogger<HomeController> logger,
|
||||
IVehicleDataAccess dataAccess,
|
||||
IUserLogic userLogic,
|
||||
IConfigHelper configuration)
|
||||
IConfigHelper configuration,
|
||||
IFileHelper fileHelper)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataAccess = dataAccess;
|
||||
_config = configuration;
|
||||
_userLogic = userLogic;
|
||||
_fileHelper = fileHelper;
|
||||
}
|
||||
private int GetUserID()
|
||||
{
|
||||
@@ -47,7 +50,13 @@ namespace CarCareTracker.Controllers
|
||||
public IActionResult Settings()
|
||||
{
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
return PartialView("_Settings", userConfig);
|
||||
var languages = _fileHelper.GetLanguages();
|
||||
var viewModel = new SettingsViewModel
|
||||
{
|
||||
UserConfig = userConfig,
|
||||
UILanguages = languages
|
||||
};
|
||||
return PartialView("_Settings", viewModel);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult WriteToSettings(UserConfig userConfig)
|
||||
|
||||
@@ -26,9 +26,9 @@ namespace CarCareTracker.Controllers
|
||||
_logger = logger;
|
||||
_loginLogic = loginLogic;
|
||||
}
|
||||
public IActionResult Index()
|
||||
public IActionResult Index(string redirectURL = "")
|
||||
{
|
||||
return View();
|
||||
return View(model: redirectURL);
|
||||
}
|
||||
public IActionResult Registration()
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,8 @@
|
||||
ReminderRecord = 5,
|
||||
NoteRecord = 6,
|
||||
SupplyRecord = 7,
|
||||
Dashboard = 8
|
||||
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
|
||||
}
|
||||
}
|
||||
24
Enum/ReminderMileageInterval.cs
Normal file
24
Enum/ReminderMileageInterval.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum ReminderMileageInterval
|
||||
{
|
||||
Other = 0,
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
UserConfig GetUserConfig(ClaimsPrincipal user);
|
||||
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
|
||||
string GetLogoUrl();
|
||||
string GetServerLanguage();
|
||||
public bool DeleteUserConfig(int userId);
|
||||
}
|
||||
public class ConfigHelper : IConfigHelper
|
||||
@@ -24,6 +26,20 @@ namespace CarCareTracker.Helper
|
||||
_userConfig = userConfig;
|
||||
_cache = memoryCache;
|
||||
}
|
||||
public string GetLogoUrl()
|
||||
{
|
||||
var logoUrl = _config["LUBELOGGER_LOGO_URL"];
|
||||
if (string.IsNullOrWhiteSpace(logoUrl))
|
||||
{
|
||||
logoUrl = "/defaults/lubelogger_logo.png";
|
||||
}
|
||||
return logoUrl;
|
||||
}
|
||||
public string GetServerLanguage()
|
||||
{
|
||||
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
return serverLanguage;
|
||||
}
|
||||
public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData)
|
||||
{
|
||||
var storedUserId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
@@ -94,7 +110,13 @@ namespace CarCareTracker.Helper
|
||||
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
|
||||
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
|
||||
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
|
||||
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
|
||||
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
|
||||
EnableAutoReminderRefresh = bool.Parse(_config[nameof(UserConfig.EnableAutoReminderRefresh)]),
|
||||
EnableAutoOdometerInsert = bool.Parse(_config[nameof(UserConfig.EnableAutoOdometerInsert)]),
|
||||
PreferredGasMileageUnit = _config[nameof(UserConfig.PreferredGasMileageUnit)],
|
||||
PreferredGasUnit = _config[nameof(UserConfig.PreferredGasUnit)],
|
||||
UserLanguage = _config[nameof(UserConfig.UserLanguage)],
|
||||
VisibleTabs = _config.GetSection("VisibleTabs").Get<List<ImportMode>>(),
|
||||
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)])
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO.Compression;
|
||||
using CarCareTracker.Models;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -6,9 +7,12 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
string GetFullFilePath(string currentFilePath, bool mustExist = true);
|
||||
string MoveFileFromTemp(string currentFilePath, string newFolder);
|
||||
bool RenameFile(string currentFilePath, string newName);
|
||||
bool DeleteFile(string currentFilePath);
|
||||
string MakeBackup();
|
||||
bool RestoreBackup(string fileName);
|
||||
bool RestoreBackup(string fileName, bool clearExisting = false);
|
||||
string MakeAttachmentsExport(List<GenericReportModel> exportData);
|
||||
List<string> GetLanguages();
|
||||
}
|
||||
public class FileHelper : IFileHelper
|
||||
{
|
||||
@@ -19,6 +23,40 @@ namespace CarCareTracker.Helper
|
||||
_webEnv = webEnv;
|
||||
_logger = logger;
|
||||
}
|
||||
public List<string> GetLanguages()
|
||||
{
|
||||
var languagePath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||
var defaultList = new List<string>() { "en_US" };
|
||||
if (Directory.Exists(languagePath))
|
||||
{
|
||||
var listOfLanguages = Directory.GetFiles(languagePath);
|
||||
if (listOfLanguages.Any())
|
||||
{
|
||||
defaultList.AddRange(listOfLanguages.Select(x => Path.GetFileNameWithoutExtension(x)));
|
||||
}
|
||||
}
|
||||
return defaultList;
|
||||
}
|
||||
public bool RenameFile(string currentFilePath, string newName)
|
||||
{
|
||||
var fullFilePath = GetFullFilePath(currentFilePath);
|
||||
if (!string.IsNullOrWhiteSpace(fullFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var originalFileName = Path.GetFileNameWithoutExtension(fullFilePath);
|
||||
var newFilePath = fullFilePath.Replace(originalFileName, newName);
|
||||
File.Move(fullFilePath, newFilePath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public string GetFullFilePath(string currentFilePath, bool mustExist = true)
|
||||
{
|
||||
if (currentFilePath.StartsWith("/"))
|
||||
@@ -38,7 +76,7 @@ namespace CarCareTracker.Helper
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
public bool RestoreBackup(string fileName)
|
||||
public bool RestoreBackup(string fileName, bool clearExisting = false)
|
||||
{
|
||||
var fullFilePath = GetFullFilePath(fileName);
|
||||
if (string.IsNullOrWhiteSpace(fullFilePath))
|
||||
@@ -64,11 +102,19 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
Directory.CreateDirectory(existingPath);
|
||||
}
|
||||
else if (clearExisting)
|
||||
{
|
||||
var filesToDelete = Directory.GetFiles(existingPath);
|
||||
foreach (string file in filesToDelete)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
//copy each files from temp folder to newPath
|
||||
var filesToUpload = Directory.GetFiles(imagePath);
|
||||
foreach(string file in filesToUpload)
|
||||
foreach (string file in filesToUpload)
|
||||
{
|
||||
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}");
|
||||
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
|
||||
}
|
||||
}
|
||||
if (Directory.Exists(documentPath))
|
||||
@@ -78,11 +124,19 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
Directory.CreateDirectory(existingPath);
|
||||
}
|
||||
else if (clearExisting)
|
||||
{
|
||||
var filesToDelete = Directory.GetFiles(existingPath);
|
||||
foreach (string file in filesToDelete)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
//copy each files from temp folder to newPath
|
||||
var filesToUpload = Directory.GetFiles(documentPath);
|
||||
foreach (string file in filesToUpload)
|
||||
{
|
||||
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}");
|
||||
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
|
||||
}
|
||||
}
|
||||
if (File.Exists(dataPath))
|
||||
@@ -100,12 +154,37 @@ namespace CarCareTracker.Helper
|
||||
File.Move(configPath, StaticHelper.UserConfigPath, true);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error Restoring Database Backup: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public string MakeAttachmentsExport(List<GenericReportModel> exportData)
|
||||
{
|
||||
var folderName = Guid.NewGuid();
|
||||
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
int fileIndex = 0;
|
||||
foreach (GenericReportModel reportModel in exportData)
|
||||
{
|
||||
foreach (UploadedFiles file in reportModel.Files)
|
||||
{
|
||||
var fileToCopy = GetFullFilePath(file.Location);
|
||||
var destFileName = $"{tempPath}/{fileIndex}{Path.GetExtension(file.Location)}";
|
||||
File.Copy(fileToCopy, destFileName);
|
||||
fileIndex++;
|
||||
}
|
||||
}
|
||||
var destFilePath = $"{tempPath}.zip";
|
||||
ZipFile.CreateFromDirectory(tempPath, destFilePath);
|
||||
//delete temp directory
|
||||
Directory.Delete(tempPath, true);
|
||||
var zipFileName = $"/temp/{folderName}.zip";
|
||||
return zipFileName;
|
||||
}
|
||||
public string MakeBackup()
|
||||
{
|
||||
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
|
||||
|
||||
@@ -5,11 +5,36 @@ 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 recordsToCalculate = results.Where(x => x.IncludeInAverage);
|
||||
if (recordsToCalculate.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
var totalMileage = recordsToCalculate.Sum(x => x.DeltaMileage);
|
||||
var totalGallons = recordsToCalculate.Sum(x => x.Gallons);
|
||||
var averageGasMileage = totalMileage / totalGallons;
|
||||
if (!useMPG && averageGasMileage > 0)
|
||||
{
|
||||
averageGasMileage = 100 / averageGasMileage;
|
||||
}
|
||||
return averageGasMileage.ToString("F");
|
||||
} catch (Exception ex)
|
||||
{
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
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,9 +67,11 @@ 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
|
||||
MissedFuelUp = currentObject.MissedFuelUp,
|
||||
Notes = currentObject.Notes,
|
||||
Tags = currentObject.Tags
|
||||
};
|
||||
if (currentObject.MissedFuelUp)
|
||||
{
|
||||
@@ -57,7 +84,17 @@ namespace CarCareTracker.Helper
|
||||
else if (currentObject.IsFillToFull)
|
||||
{
|
||||
//if user filled to full.
|
||||
if (convertedConsumption > 0.00M && deltaMileage > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
gasRecordViewModel.MilesPerGallon = useMPG ? (unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption) : 100 / ((unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
gasRecordViewModel.MilesPerGallon = 0;
|
||||
}
|
||||
}
|
||||
//reset unFactored vars
|
||||
unFactoredConsumption = 0;
|
||||
unFactoredMileage = 0;
|
||||
@@ -83,9 +120,11 @@ 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
|
||||
MissedFuelUp = currentObject.MissedFuelUp,
|
||||
Notes = currentObject.Notes,
|
||||
Tags = currentObject.Tags
|
||||
});
|
||||
}
|
||||
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
|
||||
{
|
||||
if (useAsync)
|
||||
{
|
||||
client.SendMailAsync(message, new CancellationToken());
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Send(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -4,10 +4,41 @@ 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);
|
||||
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
|
||||
{
|
||||
existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
existingReminder.Mileage += existingReminder.CustomMileageInterval;
|
||||
}
|
||||
}
|
||||
else if (existingReminder.Metric == ReminderMetric.Odometer)
|
||||
{
|
||||
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
|
||||
{
|
||||
existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval;
|
||||
} else
|
||||
{
|
||||
existingReminder.Mileage += existingReminder.CustomMileageInterval;
|
||||
}
|
||||
}
|
||||
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 +52,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,5 @@
|
||||
using CarCareTracker.Models;
|
||||
using System.Globalization;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -63,5 +64,107 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
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,
|
||||
Tags = input.Tags
|
||||
};
|
||||
}
|
||||
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,
|
||||
Tags = input.Tags
|
||||
};
|
||||
}
|
||||
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,
|
||||
Tags = input.Tags
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetFuelEconomyUnit(bool useKwh, bool useHours, bool useMPG, bool useUKMPG)
|
||||
{
|
||||
string fuelEconomyUnit;
|
||||
if (useKwh)
|
||||
{
|
||||
var distanceUnit = useHours ? "h" : (useMPG ? "mi." : "km");
|
||||
fuelEconomyUnit = useMPG ? $"{distanceUnit}/kWh" : $"kWh/100{distanceUnit}";
|
||||
}
|
||||
else if (useMPG && useUKMPG)
|
||||
{
|
||||
fuelEconomyUnit = useHours ? "h/g" : "mpg";
|
||||
}
|
||||
else if (useUKMPG)
|
||||
{
|
||||
fuelEconomyUnit = useHours ? "l/100h" : "l/100mi.";
|
||||
}
|
||||
else
|
||||
{
|
||||
fuelEconomyUnit = useHours ? (useMPG ? "h/g" : "l/100h") : (useMPG ? "mpg" : "l/100km");
|
||||
}
|
||||
return fuelEconomyUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
Helper/TranslationHelper.cs
Normal file
62
Helper/TranslationHelper.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
public interface ITranslationHelper
|
||||
{
|
||||
string Translate(string userLanguage, string text);
|
||||
}
|
||||
public class TranslationHelper : ITranslationHelper
|
||||
{
|
||||
private readonly IFileHelper _fileHelper;
|
||||
private readonly IConfiguration _config;
|
||||
private IMemoryCache _cache;
|
||||
public TranslationHelper(IFileHelper fileHelper, IConfiguration config, IMemoryCache memoryCache)
|
||||
{
|
||||
_fileHelper = fileHelper;
|
||||
_config = config;
|
||||
_cache = memoryCache;
|
||||
}
|
||||
public string Translate(string userLanguage, string text)
|
||||
{
|
||||
bool create = bool.Parse(_config["LUBELOGGER_TRANSLATOR"] ?? "false");
|
||||
//transform input text into key.
|
||||
string translationKey = text.Replace(" ", "_");
|
||||
var translationFilePath = userLanguage == "en_US" ? _fileHelper.GetFullFilePath($"/defaults/en_US.json") : _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
|
||||
var dictionary = _cache.GetOrCreate<Dictionary<string, string>>($"lang_{userLanguage}", entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromHours(1);
|
||||
if (File.Exists(translationFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var translationFile = File.ReadAllText(translationFilePath);
|
||||
var translationDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(translationFile);
|
||||
return translationDictionary ?? new Dictionary<string, string>();
|
||||
} catch (Exception ex)
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
});
|
||||
if (dictionary != null && dictionary.ContainsKey(translationKey))
|
||||
{
|
||||
return dictionary[translationKey];
|
||||
}
|
||||
else if (create && File.Exists(translationFilePath))
|
||||
{
|
||||
//create entry
|
||||
dictionary.Add(translationKey, text);
|
||||
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(dictionary));
|
||||
return text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ namespace CarCareTracker.MapProfile
|
||||
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"]);
|
||||
@@ -20,6 +22,10 @@ namespace CarCareTracker.MapProfile
|
||||
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"]);
|
||||
Map(m => m.Tags).Name(["tags"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,26 @@ namespace CarCareTracker.Middleware
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
if (Request.Path.Value == "/Vehicle/Index" && Request.QueryString.HasValue)
|
||||
{
|
||||
Response.Redirect($"/Login/Index?redirectURL={Request.Path.Value}{Request.QueryString.Value}");
|
||||
} else
|
||||
{
|
||||
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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
public decimal Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public CollisionRecord ToCollisionRecord() { return new CollisionRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public CollisionRecord ToCollisionRecord() { return new CollisionRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
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 List<string> Tags { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
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 List<string> Tags { get; set; } = new List<string>();
|
||||
public GasRecord ToGasRecord() { return new GasRecord {
|
||||
Id = Id,
|
||||
Cost = Cost,
|
||||
@@ -26,7 +28,9 @@
|
||||
VehicleId = VehicleId,
|
||||
Files = Files,
|
||||
IsFillToFull = IsFillToFull,
|
||||
MissedFuelUp = MissedFuelUp
|
||||
MissedFuelUp = MissedFuelUp,
|
||||
Notes = Notes,
|
||||
Tags = Tags
|
||||
}; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
public class GasRecordInputContainer
|
||||
{
|
||||
public bool UseKwh { get; set; }
|
||||
public bool UseHours { get; set; }
|
||||
public GasRecordInput GasRecord { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,8 @@
|
||||
public decimal CostPerGallon { get; set; }
|
||||
public bool IsFillToFull { get; set; }
|
||||
public bool MissedFuelUp { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp); } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
public class GasRecordViewModelContainer
|
||||
{
|
||||
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; }
|
||||
@@ -10,5 +10,6 @@
|
||||
public decimal Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<string> Tags { get; set;} = new List<string>();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -18,6 +23,7 @@
|
||||
public string PartNumber { get; set; }
|
||||
public string PartSupplier { get; set; }
|
||||
public string PartQuantity { get; set; }
|
||||
public string Tags { get; set; }
|
||||
}
|
||||
|
||||
public class SupplyRecordExportModel
|
||||
@@ -38,14 +44,22 @@
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public string Tags { get; set; }
|
||||
}
|
||||
public class OdometerRecordExportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
}
|
||||
public class TaxRecordExportModel
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Cost { get; set; }
|
||||
public string Tags { get; set; }
|
||||
}
|
||||
public class GasRecordExportModel
|
||||
{
|
||||
@@ -56,6 +70,8 @@
|
||||
public string FuelEconomy { get; set; }
|
||||
public string IsFillToFull { get; set; }
|
||||
public string MissedFuelUp { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Tags { get; set; }
|
||||
}
|
||||
public class ReminderExportModel
|
||||
{
|
||||
@@ -64,4 +80,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,8 @@
|
||||
public int VehicleId { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string NoteText { get; set; }
|
||||
public bool Pinned { get; set; }
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
}
|
||||
|
||||
13
Models/OdometerRecord/OdometerRecord.cs
Normal file
13
Models/OdometerRecord/OdometerRecord.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
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<string> Tags { get; set; } = new List<string>();
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
}
|
||||
}
|
||||
14
Models/OdometerRecord/OdometerRecordInput.cs
Normal file
14
Models/OdometerRecord/OdometerRecordInput.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
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 List<string> Tags { get; set; } = new List<string>();
|
||||
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
}
|
||||
}
|
||||
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,10 @@
|
||||
public int Mileage { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public int CustomMileageInterval { get; set; } = 0;
|
||||
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
|
||||
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
public int Mileage { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool IsRecurring { get; set; } = false;
|
||||
public int CustomMileageInterval { get; set; } = 0;
|
||||
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 +20,10 @@
|
||||
Mileage = Mileage,
|
||||
Description = Description,
|
||||
Metric = Metric,
|
||||
IsRecurring = IsRecurring,
|
||||
ReminderMileageInterval = ReminderMileageInterval,
|
||||
ReminderMonthInterval = ReminderMonthInterval,
|
||||
CustomMileageInterval = CustomMileageInterval,
|
||||
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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
public decimal Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
}
|
||||
}
|
||||
|
||||
8
Models/Settings/SettingsViewModel.cs
Normal file
8
Models/Settings/SettingsViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SettingsViewModel
|
||||
{
|
||||
public UserConfig UserConfig { get; set; }
|
||||
public List<string> UILanguages { get; set; }
|
||||
}
|
||||
}
|
||||
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,9 @@
|
||||
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>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,21 @@
|
||||
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 List<string> Tags { get; set; } = new List<string>();
|
||||
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,
|
||||
Tags = Tags
|
||||
}; }
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@
|
||||
public decimal Cost { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||
public List<SupplyUsage> Supplies { get; set; } = new List<SupplyUsage>();
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files, Tags = Tags }; }
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,14 @@
|
||||
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 PreferredGasUnit { get; set; } = string.Empty;
|
||||
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
||||
public string UserNameHash { get; set; }
|
||||
public string UserPasswordHash { get; set;}
|
||||
public string UserLanguage { get; set; } = "en_US";
|
||||
public List<ImportMode> VisibleTabs { get; set; } = new List<ImportMode>() {
|
||||
ImportMode.Dashboard,
|
||||
ImportMode.ServiceRecord,
|
||||
|
||||
@@ -9,5 +9,7 @@
|
||||
public string Model { get; set; }
|
||||
public string LicensePlate { get; set; }
|
||||
public bool IsElectric { get; set; } = false;
|
||||
public bool UseHours { get; set; } = false;
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
16
Program.cs
16
Program.cs
@@ -5,6 +5,8 @@ using CarCareTracker.Logic;
|
||||
using CarCareTracker.Middleware;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -23,6 +25,8 @@ 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>();
|
||||
@@ -31,6 +35,7 @@ builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
|
||||
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
|
||||
builder.Services.AddSingleton<IMailHelper, MailHelper>();
|
||||
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
|
||||
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
|
||||
|
||||
//configure logic
|
||||
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
|
||||
@@ -52,6 +57,17 @@ builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.DefaultPolicy = new AuthorizationPolicyBuilder().AddAuthenticationSchemes("AuthN").RequireAuthenticatedUser().Build();
|
||||
});
|
||||
//Configure max file upload size
|
||||
builder.Services.Configure<KestrelServerOptions>(options =>
|
||||
{
|
||||
options.Limits.MaxRequestBodySize = int.MaxValue; // if don't set default value is: 30 MB
|
||||
});
|
||||
builder.Services.Configure<FormOptions>(options =>
|
||||
{
|
||||
options.ValueLengthLimit = int.MaxValue;
|
||||
options.MultipartBodyLengthLimit = int.MaxValue; // if don't set default value is: 128 MB
|
||||
options.MultipartHeadersLengthLimit = int.MaxValue;
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
17
README.md
17
README.md
@@ -2,11 +2,21 @@
|
||||
|
||||
A self-hosted, open-source vehicle service records and maintainence tracker.
|
||||
|
||||
Support this project on Patreon: https://patreon.com/LubeLogger
|
||||
Visit our website: https://lubelogger.com
|
||||
|
||||
Support this project by [Subscribing on Patreon](https://patreon.com/LubeLogger) or [Making a Donation](https://buy.stripe.com/aEU9Egc8DdMc9bO144)
|
||||
|
||||
## 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
|
||||
@@ -14,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)
|
||||
@@ -28,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
|
||||
@@ -125,3 +239,69 @@
|
||||
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>
|
||||
@@ -1,10 +1,13 @@
|
||||
@{
|
||||
@using CarCareTracker.Helper
|
||||
@{
|
||||
ViewData["Title"] = "Admin";
|
||||
}
|
||||
@inject IConfiguration config;
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
bool emailServerIsSetup = true;
|
||||
var mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
|
||||
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer))
|
||||
{
|
||||
emailServerIsSetup = false;
|
||||
@@ -17,31 +20,31 @@
|
||||
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
|
||||
</div>
|
||||
<div class="col-11">
|
||||
<span class="display-6">Admin Panel</span>
|
||||
<span class="display-6">@translator.Translate(userLanguage, "Admin Panel")</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-md-5 col-12">
|
||||
<span class="lead">Tokens</span>
|
||||
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<button onclick="generateNewToken()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Generate User Token</button>
|
||||
<button onclick="generateNewToken()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Generate User Token")</button>
|
||||
</div>
|
||||
<div class="col-6 d-flex align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="enableAutoNotify" @(emailServerIsSetup ? "checked" : "disabled")>
|
||||
<label class="form-check-label" for="enableAutoNotify">Auto Notify(via Email)</label>
|
||||
<label class="form-check-label" for="enableAutoNotify">@translator.Translate(userLanguage, "Auto Notify(via Email)")</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<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>
|
||||
<th scope="col" class="col-2">Delete</th>
|
||||
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Token")</th>
|
||||
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Issued To")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -59,15 +62,15 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-12 col-md-7">
|
||||
<span class="lead">Users</span>
|
||||
<span class="lead">@translator.Translate(userLanguage, "Users")</span>
|
||||
<hr />
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-4">Username</th>
|
||||
<th scope="col" class="col-4">Email</th>
|
||||
<th scope="col" class="col-2">Is Admin</th>
|
||||
<th scope="col" class="col-2">Delete</th>
|
||||
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Username")</th>
|
||||
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Email")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Is Admin")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -92,7 +95,7 @@
|
||||
if (data){
|
||||
reloadPage();
|
||||
} else {
|
||||
errorToast("An error has occurred, please try again later.");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -104,7 +107,7 @@
|
||||
if (data) {
|
||||
reloadPage();
|
||||
} else {
|
||||
errorToast("An error has occurred, please try again later.");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -113,7 +116,7 @@
|
||||
if (data) {
|
||||
reloadPage();
|
||||
} else {
|
||||
errorToast("An error has occurred, please try again later.");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var enableAuth = config.GetUserConfig(User).EnableAuth;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableAuth = userConfig.EnableAuth;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
}
|
||||
@model string
|
||||
@{
|
||||
ViewData["Title"] = "LubeLogger";
|
||||
}
|
||||
@section Scripts {
|
||||
<script src="~/js/garage.js" asp-append-version="true"></script>
|
||||
<script src="~/js/garage.js"></script>
|
||||
}
|
||||
<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 user-select-none @(Model == "garage" ? "active" : "")" ontouchstart="detectLongTouch(this)" ontouchend="detectTouchEndPremature(this)" 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>@translator.Translate(userLanguage,"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>@translator.Translate(userLanguage,"Settings")</span></button>
|
||||
</li>
|
||||
@if (User.IsInRole("CookieAuth"))
|
||||
{
|
||||
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
||||
{
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="dropdown-item" href="/Admin"><span class="display-3 ms-2"><i class="bi bi-people me-2"></i>Admin Panel</span></a>
|
||||
<a class="dropdown-item" href="/Admin"><span class="display-3 ms-2"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</span></a>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" onclick="performLogOut()"><span class="display-3 ms-2"><i class="bi bi-box-arrow-right me-2"></i>Logout</span></button>
|
||||
<button class="nav-link" onclick="performLogOut()"><span class="display-3 ms-2"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</span></button>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -35,7 +39,7 @@
|
||||
<div class="container">
|
||||
<div class="row mt-2">
|
||||
<div class="d-flex lubelogger-navbar">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@logoUrl" />
|
||||
<div class="lubelogger-navbar-button">
|
||||
<button type="button" class="btn btn-dark" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||
</div>
|
||||
@@ -44,10 +48,10 @@
|
||||
<hr />
|
||||
<ul class="nav nav-tabs lubelogger-tab" id="homeTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @(Model == "garage" ? "active" : "")" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front me-2"></i>Garage</button>
|
||||
<button class="nav-link @(Model == "garage" ? "active" : "")" oncontextmenu="sortGarage(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front me-2"></i>@translator.Translate(userLanguage,"Garage")</button>
|
||||
</li>
|
||||
<li class="nav-item ms-auto" role="presentation">
|
||||
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear me-2"></i>Settings</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"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</button>
|
||||
</li>
|
||||
@if (User.IsInRole("CookieAuth"))
|
||||
{
|
||||
@@ -57,11 +61,11 @@
|
||||
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
||||
{
|
||||
<li>
|
||||
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>Admin Panel</a>
|
||||
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</a>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>Logout</button>
|
||||
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -69,9 +73,8 @@
|
||||
</ul>
|
||||
<div class="tab-content" id="homeTab">
|
||||
<div class="tab-pane fade @(Model == "garage" ? "show active" : "")" id="garage-tab-pane" role="tabpanel" tabindex="0">
|
||||
<div class="row">
|
||||
<div id="garageContainer" class="row gy-3 align-items-stretch">
|
||||
</div>
|
||||
<div id="garageContainer">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade @(Model == "settings" ? "show active" : "")" id="settings-tab-pane" role="tabpanel" tabindex="0">
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
@model List<Vehicle>
|
||||
|
||||
@if (Model.Any())
|
||||
@{
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
}
|
||||
@if (recordTags.Any())
|
||||
{
|
||||
foreach (Vehicle vehicle in Model)
|
||||
<div class='row'>
|
||||
<div class="d-flex align-items-center flex-wrap mt-4">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4">
|
||||
<span onclick="filterGarage(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="row gy-3 align-items-stretch vehiclesContainer">
|
||||
@foreach (Vehicle vehicle in Model)
|
||||
{
|
||||
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none garage-item" data-tags='@string.Join(" ", vehicle.Tags)' 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">
|
||||
<h5 class="card-title text-truncate">@($"{vehicle.Year}")</h5>
|
||||
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>
|
||||
<h5 class="card-title text-truncate">@($"{vehicle.Make}")</h5>
|
||||
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5>
|
||||
<p class="card-text text-truncate">@vehicle.LicensePlate</p>
|
||||
@@ -16,9 +35,10 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<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 garage-item-add">
|
||||
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
||||
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,134 +1,185 @@
|
||||
@using CarCareTracker.Helper
|
||||
@model UserConfig
|
||||
@model SettingsViewModel
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userLanguage = Model.UserConfig.UserLanguage;
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-center">
|
||||
<h6 class="display-6 mt-2">Settings</h6>
|
||||
<h6 class="display-6 mt-2">@translator.Translate(userLanguage, "Settings")</h6>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="col-12 col-md-6">
|
||||
<input id="preferredGasUnit" style="display:none;" value="@Model.UserConfig.PreferredGasUnit" />
|
||||
<input id="preferredFuelMileageUnit" style="display:none;" value="@Model.UserConfig.PreferredGasMileageUnit" />
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableDarkMode" checked="@Model.UseDarkMode">
|
||||
<label class="form-check-label" for="enableDarkMode">Dark Mode</label>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableDarkMode" checked="@Model.UserConfig.UseDarkMode">
|
||||
<label class="form-check-label" for="enableDarkMode">@translator.Translate(userLanguage, "Dark Mode")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableCsvImports" checked="@Model.EnableCsvImports">
|
||||
<label class="form-check-label" for="enableCsvImports">Enable CSV Imports</label>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableCsvImports" checked="@Model.UserConfig.EnableCsvImports">
|
||||
<label class="form-check-label" for="enableCsvImports">@translator.Translate(userLanguage, "Enable CSV Imports")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMPG" checked="@Model.UseMPG">
|
||||
<label class="form-check-label" for="useMPG">Use Imperial Calculation for Fuel Economy Calculations(MPG)<br /><small class="text-body-secondary">This Will Also Change Units to Miles and Gallons</small></label>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMPG" checked="@Model.UserConfig.UseMPG">
|
||||
<label class="form-check-label" for="useMPG">@translator.Translate(userLanguage, "Use Imperial Calculation for Fuel Economy Calculations(MPG)")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "This Will Also Change Units to Miles and Gallons")</small></label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<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>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUKMPG" checked="@Model.UserConfig.UseUKMPG">
|
||||
<label class="form-check-label" for="useUKMPG">@translator.Translate(userLanguage, "Use UK MPG Calculation")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation")</small></label>
|
||||
</div>
|
||||
<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>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UserConfig.UseDescending">
|
||||
<label class="form-check-label" for="useDescending">@translator.Translate(userLanguage, "Sort lists in Descending Order(Newest to Oldest)")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<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>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.UserConfig.HideZero">
|
||||
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 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>
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
|
||||
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "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.UserConfig.UseMarkDownOnSavedNotes">
|
||||
<label class="form-check-label" for="useMarkDownOnSavedNotes">@translator.Translate(userLanguage, "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.UserConfig.EnableAutoReminderRefresh">
|
||||
<label class="form-check-label" for="enableAutoReminderRefresh">@translator.Translate(userLanguage, "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.UserConfig.EnableAutoOdometerInsert">
|
||||
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "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">
|
||||
<input class="form-check-input" onChange="enableAuthCheckChanged()" type="checkbox" role="switch" id="enableAuth" checked="@Model.EnableAuth">
|
||||
<label class="form-check-label" for="enableAuth">Enable Authentication</label>
|
||||
<input class="form-check-input" onChange="enableAuthCheckChanged()" type="checkbox" role="switch" id="enableAuth" checked="@Model.UserConfig.EnableAuth">
|
||||
<label class="form-check-label" for="enableAuth">@translator.Translate(userLanguage, "Enable Authentication")</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="row" id="visibleTabs">
|
||||
<div class="col-12">
|
||||
<span class="lead">Visible Tabs</span>
|
||||
<span class="lead">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ServiceRecord" id="serviceRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.ServiceRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="serviceRecordTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" disabled class="form-check-input me-1" type="checkbox" value="Dashboard" id="dashboardTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.Dashboard) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="dashboardTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="RepairRecord" id="repairRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.RepairRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="repairRecordTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="UpgradeRecord" id="upgradeRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.UpgradeRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="upgradeRecordTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="GasRecord" id="gasRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.GasRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="gasRecordTab">@translator.Translate(userLanguage, "Fuel")</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="OdometerRecord" id="odometerRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.OdometerRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="odometerRecordTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="TaxRecord" id="taxRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.TaxRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="taxRecordTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="NoteRecord" id="noteRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.NoteRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="noteRecordTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ReminderRecord" id="reminderRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.ReminderRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="reminderRecordTab">@translator.Translate(userLanguage, "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>
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="SupplyRecord" id="supplyRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.SupplyRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="supplyRecordTab">@translator.Translate(userLanguage, "Supplies")</label>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="PlanRecord" id="planRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.PlanRecord) ? "checked" : "")>
|
||||
<label class="form-check-label stretched-link" for="planRecordTab">@translator.Translate(userLanguage, "Planner")</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="lead">Default Tab</span>
|
||||
<span class="lead">@translator.Translate(userLanguage, "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.UserConfig, ImportMode.Dashboard)) value="Dashboard">@translator.Translate(userLanguage, "Dashboard")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.ServiceRecord)) value="ServiceRecord">@translator.Translate(userLanguage, "Service Record")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.RepairRecord)) value="RepairRecord">@translator.Translate(userLanguage, "Repairs")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.UpgradeRecord)) value="UpgradeRecord">@translator.Translate(userLanguage, "Upgrades")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.GasRecord)) value="GasRecord">@translator.Translate(userLanguage, "Fuel")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.TaxRecord)) value="TaxRecord">@translator.Translate(userLanguage, "Tax")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.NoteRecord)) value="NoteRecord">@translator.Translate(userLanguage, "Notes")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.ReminderRecord)) value="ReminderRecord">@translator.Translate(userLanguage, "Reminders")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.SupplyRecord)) value="SupplyRecord">@translator.Translate(userLanguage, "Supplies")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.PlanRecord)) value="PlanRecord">@translator.Translate(userLanguage, "Planner")</!option>
|
||||
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.OdometerRecord)) value="OdometerRecord">@translator.Translate(userLanguage, "Odometer")</!option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="lead">@translator.Translate(userLanguage, "Language")</span>
|
||||
<select class="form-select" onchange="updateSettings()" id="defaultLanguage">
|
||||
@foreach (string uiLanguage in Model.UILanguages)
|
||||
{
|
||||
<!option @(Model.UserConfig.UserLanguage == uiLanguage ? "selected" : "")>@uiLanguage</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="lead">Backups</span>
|
||||
<span class="lead">@translator.Translate(userLanguage, "Backups")</span>
|
||||
<div class="row">
|
||||
<div class="col-6 d-grid">
|
||||
<button onclick="makeBackup()" class="btn btn-primary btn-md">Make</button>
|
||||
<button onclick="makeBackup()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "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>
|
||||
<button onclick="openRestoreBackup()" class="btn btn-secondary btn-md">@translator.Translate(userLanguage, "Restore")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<span class="lead">@translator.Translate(userLanguage, "Manage Languages")</span>
|
||||
<div class="row">
|
||||
<div class="col-6 d-grid">
|
||||
<input onChange="uploadLanguage(this)" type="file" accept=".json" class="d-none" id="inputLanguage">
|
||||
<button onclick="openUploadLanguage()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Upload")</button>
|
||||
</div>
|
||||
<div class="col-6 d-grid">
|
||||
<button onclick="deleteLanguage()" @(Model.UserConfig.UserLanguage == "en_US" ? "disabled" : "") class="btn btn-danger btn-md">@translator.Translate(userLanguage, "Delete")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="d-flex justify-content-center">
|
||||
<h6 class="display-6 mt-2">About</h6>
|
||||
<h6 class="display-6 mt-2">@translator.Translate(userLanguage, "About")</h6>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="col-12 col-md-6">
|
||||
@@ -136,14 +187,14 @@
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<small class="text-body-secondary">Version 1.0.6</small>
|
||||
<small class="text-body-secondary">Version 1.1.1</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.<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>
|
||||
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> or make a <a class="link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" href="https://buy.stripe.com/aEU9Egc8DdMc9bO144" target="_blank">donation</a>
|
||||
</p>
|
||||
<div class="d-flex justify-content-center">
|
||||
<h6 class="display-7 mt-2">Hometown Shoutout</h6>
|
||||
@@ -169,6 +220,7 @@
|
||||
<li class="list-group-item">SweetAlert2</li>
|
||||
<li class="list-group-item">CsvHelper</li>
|
||||
<li class="list-group-item">Chart.js</li>
|
||||
<li class="list-group-item">Drawdown</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,10 +231,18 @@
|
||||
});
|
||||
return visibleTabs.toArray();
|
||||
}
|
||||
function updateSettings(){
|
||||
function deleteLanguage() {
|
||||
var languageFileLocation = `/translations/${$("#defaultLanguage").val()}.json`;
|
||||
$.post('/Files/DeleteFiles', { fileLocation: languageFileLocation }, function (data) {
|
||||
//reset user language back to en_US
|
||||
$("#defaultLanguage").val('en_US');
|
||||
updateSettings();
|
||||
});
|
||||
}
|
||||
function updateSettings() {
|
||||
var visibleTabs = getCheckedTabs();
|
||||
var defaultTab = $("#defaultTab").val();
|
||||
if (!visibleTabs.includes(defaultTab)){
|
||||
if (!visibleTabs.includes(defaultTab)) {
|
||||
defaultTab = "Dashboard"; //default to dashboard.
|
||||
}
|
||||
var userConfigObject = {
|
||||
@@ -193,16 +253,22 @@
|
||||
hideZero: $("#hideZero").is(":checked"),
|
||||
useUKMpg: $("#useUKMPG").is(":checked"),
|
||||
useThreeDecimalGasCost: $("#useThreeDecimal").is(":checked"),
|
||||
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
|
||||
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
|
||||
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
|
||||
preferredGasUnit: $("#preferredGasUnit").val(),
|
||||
preferredGasMileageUnit: $("#preferredFuelMileageUnit").val(),
|
||||
userLanguage: $("#defaultLanguage").val(),
|
||||
visibleTabs: visibleTabs,
|
||||
defaultTab: defaultTab
|
||||
}
|
||||
sloader.show();
|
||||
$.post('/Home/WriteToSettings', { userConfig: userConfigObject}, function(data){
|
||||
$.post('/Home/WriteToSettings', { userConfig: userConfigObject }, function (data) {
|
||||
sloader.hide();
|
||||
if (data) {
|
||||
setTimeout(function () { window.location.href = '/Home/Index?tab=settings' }, 500);
|
||||
} else {
|
||||
errorToast("An error occurred, please try again later.")
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -211,9 +277,37 @@
|
||||
window.location.href = data;
|
||||
});
|
||||
}
|
||||
function openRestoreBackup(){
|
||||
function openUploadLanguage(){
|
||||
$("#inputLanguage").click();
|
||||
}
|
||||
function openRestoreBackup() {
|
||||
$("#inputBackup").click();
|
||||
}
|
||||
function uploadLanguage(event){
|
||||
let formData = new FormData();
|
||||
formData.append("file", event.files[0]);
|
||||
sloader.show();
|
||||
$.ajax({
|
||||
url: "/Files/HandleTranslationFileUpload",
|
||||
data: formData,
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
success: function (response) {
|
||||
sloader.hide();
|
||||
if (response.success) {
|
||||
setTimeout(function () { window.location.href = '/Home/Index?tab=settings' }, 500);
|
||||
} else {
|
||||
errorToast(response.message);
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
sloader.hide();
|
||||
errorToast("An error has occurred, please check the file size and try again later.");
|
||||
}
|
||||
});
|
||||
}
|
||||
function restoreBackup(event) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", event.files[0]);
|
||||
@@ -227,20 +321,24 @@
|
||||
type: 'POST',
|
||||
success: function (response) {
|
||||
if (response.trim() != '') {
|
||||
$.post('/Files/RestoreBackup', { fileName : response}, function (data) {
|
||||
$.post('/Files/RestoreBackup', { fileName: response }, function (data) {
|
||||
sloader.hide();
|
||||
if (data){
|
||||
if (data) {
|
||||
successToast("Backup Restored");
|
||||
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
|
||||
} else {
|
||||
errorToast("An error occurred, please try again later.");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
sloader.hide();
|
||||
errorToast("An error has occurred, please check the file size and try again later.");
|
||||
}
|
||||
});
|
||||
}
|
||||
function enableAuthCheckChanged(){
|
||||
function enableAuthCheckChanged() {
|
||||
var enableAuth = $("#enableAuth").is(":checked");
|
||||
if (enableAuth) {
|
||||
//swal dialog to set up username and password.
|
||||
@@ -266,7 +364,7 @@
|
||||
if (data) {
|
||||
setTimeout(function () { window.location.href = '/Login' }, 500);
|
||||
} else {
|
||||
errorToast("An error occurred, please try again later.");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -276,7 +374,7 @@
|
||||
if (data) {
|
||||
setTimeout(function () { window.location.href = '/Home' }, 1000);
|
||||
} else {
|
||||
errorToast("An error occurred, please try again later.");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
@{
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
ViewData["Title"] = "LubeLogger - Login";
|
||||
}
|
||||
@section Scripts {
|
||||
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||
<script src="~/js/login.js"></script>
|
||||
}
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@logoUrl" />
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">Username</label>
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>Request</button>
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Request")</button>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<a href="/Login/ResetPassword" class="btn btn-link mt-2">I Have a Token</a>
|
||||
<a href="/Login/ResetPassword" class="btn btn-link mt-2">@translator.Translate(userLanguage, "I Have a Token")</a>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
|
||||
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,47 @@
|
||||
@{
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model string
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
ViewData["Title"] = "LubeLogger - Login";
|
||||
}
|
||||
@section Scripts {
|
||||
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||
<script src="~/js/login.js"></script>
|
||||
}
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@logoUrl" />
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">Username</label>
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserPassword">Password</label>
|
||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||
<input type="password" id="inputUserPassword" onkeyup="handlePasswordKeyPress(event)" class="form-control">
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="inputPersistent">
|
||||
<label class="form-check-label" for="inputPersistent">Remember Me</label>
|
||||
<label class="form-check-label" for="inputPersistent">@translator.Translate(userLanguage, "Remember Me")</label>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="performLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>Login</button>
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="performLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Login")</button>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<a href="/Login/ForgotPassword" class="btn btn-link mt-2">Forgot Password</a>
|
||||
<a href="/Login/ForgotPassword" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Forgot Password")</a>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<a href="/Login/Registration" class="btn btn-link mt-2">Register</a>
|
||||
<a href="/Login/Registration" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Register")</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function getRedirectURL(){
|
||||
return { url: decodeHTMLEntities('@Model') };
|
||||
}
|
||||
</script>
|
||||
@@ -1,34 +1,41 @@
|
||||
@{
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
ViewData["Title"] = "LubeLogger - Register";
|
||||
}
|
||||
@section Scripts {
|
||||
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||
<script src="~/js/login.js"></script>
|
||||
}
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@logoUrl" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">Token</label>
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">Email Address</label>
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
|
||||
<input type="text" id="inputEmail" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">Username</label>
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserPassword">Password</label>
|
||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||
<input type="password" id="inputUserPassword" class="form-control">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="performRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>Register</button>
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="performRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Register")</button>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
|
||||
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
@{
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
ViewData["Title"] = "LubeLogger - Register";
|
||||
}
|
||||
@section Scripts {
|
||||
<script src="~/js/login.js" asp-append-version="true"></script>
|
||||
<script src="~/js/login.js"></script>
|
||||
}
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@logoUrl" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">Token</label>
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">Email Address</label>
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
|
||||
<input type="text" id="inputEmail" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputUserPassword">New Password</label>
|
||||
<label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label>
|
||||
<input type="password" id="inputUserPassword" class="form-control">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="performPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>Reset Password</button>
|
||||
<button type="button" class="btn btn-warning mt-2" onclick="performPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Reset Password")</button>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<a href="/Login/Index" class="btn btn-link mt-2">Back to Login</a>
|
||||
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
<h1>Access Denied</h1>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
|
||||
</div>
|
||||
<div class="col-11">
|
||||
<h1>Access Denied</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,11 +1,17 @@
|
||||
@using CarCareTracker.Helper
|
||||
<!DOCTYPE html>
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var useDarkMode = userConfig.UseDarkMode;
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var useMPG = userConfig.UseMPG;
|
||||
var useMarkDown = userConfig.UseMarkDownOnSavedNotes;
|
||||
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
|
||||
var shortDatePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||
var numberFormat = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
shortDatePattern = shortDatePattern.ToLower();
|
||||
if (!shortDatePattern.Contains("dd"))
|
||||
{
|
||||
@@ -27,9 +33,10 @@
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
|
||||
<link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/loader.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/sweetalert/sweetalert2.min.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/lib/bootstrap-tagsinput/bootstrap-tagsinput.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css"/>
|
||||
<link rel="stylesheet" href="~/css/loader.css"/>
|
||||
<link rel="stylesheet" href="~/sweetalert/sweetalert2.min.css"/>
|
||||
<link rel="icon" sizes="192x192" href="~/defaults/lubelogger_icon_192.png" />
|
||||
<link rel="icon" sizes="128x128" href="~/defaults/lubelogger_icon_128.png" />
|
||||
<link rel="apple-touch-icon" sizes="128x128" href="~/defaults/lubelogger_icon_128.png" />
|
||||
@@ -40,13 +47,18 @@
|
||||
<script src="~/js/shared.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/lib/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
|
||||
<script src="~/lib/bootstrap-tagsinput/bootstrap-tagsinput.js"></script>
|
||||
<script src="~/sweetalert/sweetalert2.all.min.js"></script>
|
||||
<script src="~/js/loader.js"></script>
|
||||
<script>
|
||||
function getGlobalConfig() {
|
||||
return {
|
||||
useDarkMode : "@useDarkMode" == "True",
|
||||
enableCsvImport : "@enableCsvImports" == "True"
|
||||
enableCsvImport : "@enableCsvImports" == "True",
|
||||
useMarkDown: "@useMarkDown" == "True",
|
||||
currencySymbol: decodeHTMLEntities("@numberFormat.CurrencySymbol"),
|
||||
useThreeDecimals: "@useThreeDecimals" == "True",
|
||||
useMPG: "@useMPG" == "True"
|
||||
}
|
||||
}
|
||||
function getShortDatePattern() {
|
||||
@@ -54,6 +66,30 @@
|
||||
pattern: "@shortDatePattern"
|
||||
}
|
||||
}
|
||||
function globalParseFloat(input){
|
||||
//remove thousands separator.
|
||||
var thousandSeparator = "@numberFormat.NumberGroupSeparator";
|
||||
var decimalSeparator = "@numberFormat.NumberDecimalSeparator";
|
||||
var currencySymbol = decodeHTMLEntities("@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;
|
||||
}
|
||||
function genericErrorMessage(){
|
||||
return decodeHTMLEntities('@translator.Translate(userLanguage, "An error has occurred, please try again later")');
|
||||
}
|
||||
</script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</head>
|
||||
|
||||
@@ -3,60 +3,71 @@
|
||||
ViewData["Title"] = "LubeLogger - View Vehicle";
|
||||
}
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model Vehicle
|
||||
@section Scripts {
|
||||
<script src="~/js/vehicle.js" asp-append-version="true"></script>
|
||||
<script src="~/js/servicerecord.js" asp-append-version="true"></script>
|
||||
<script src="~/js/gasrecord.js" asp-append-version="true"></script>
|
||||
<script src="~/js/collisionrecord.js" asp-append-version="true"></script>
|
||||
<script src="~/js/taxrecord.js" asp-append-version="true"></script>
|
||||
<script src="~/js/reminderrecord.js" asp-append-version="true"></script>
|
||||
<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/vehicle.js"></script>
|
||||
<script src="~/js/servicerecord.js"></script>
|
||||
<script src="~/js/gasrecord.js"></script>
|
||||
<script src="~/js/collisionrecord.js"></script>
|
||||
<script src="~/js/taxrecord.js"></script>
|
||||
<script src="~/js/reminderrecord.js"></script>
|
||||
<script src="~/js/upgraderecord.js"></script>
|
||||
<script src="~/js/note.js"></script>
|
||||
<script src="~/js/reports.js"></script>
|
||||
<script src="~/js/supplyrecord.js"></script>
|
||||
<script src="~/js/planrecord.js"></script>
|
||||
<script src="~/js/odometerrecord.js"></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">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" onclick="returnToGarage()"><span class="display-3 ms-2"><i class="bi bi-arrow-left-square"></i>Garage</span></button>
|
||||
<button class="nav-link" onclick="returnToGarage()"><span class="display-3 ms-2"><i class="bi bi-arrow-left-square me-2"></i>@translator.Translate(userLanguage,"Garage")</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<button class="nav-link" onclick="editVehicle(@Model.Id)"><span class="display-3 ms-2"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Edit Vehicle")</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Dashboard")</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Planner")</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Odometer")</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Service Records")</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage,"Repairs")</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Upgrades")</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>
|
||||
<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>@translator.Translate(userLanguage, "Fuel")</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>
|
||||
<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>@translator.Translate(userLanguage, "Supplies")</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>
|
||||
<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>@translator.Translate(userLanguage, "Taxes")</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>
|
||||
<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>@translator.Translate(userLanguage, "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>@translator.Translate(userLanguage, "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>@translator.Translate(userLanguage, "Delete Vehicle")</span></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -74,36 +85,42 @@
|
||||
<hr />
|
||||
<ul class="nav nav-tabs lubelogger-tab" id="vehicleTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage,"Dashboard")</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Planner")</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Odometer")</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Service Records")</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Repairs")</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
<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>@translator.Translate(userLanguage, "Upgrades")</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>
|
||||
<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>@translator.Translate(userLanguage, "Fuel")</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>
|
||||
<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>@translator.Translate(userLanguage, "Supplies")</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>
|
||||
<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>@translator.Translate(userLanguage, "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>@translator.Translate(userLanguage, "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>@translator.Translate(userLanguage, "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>
|
||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">@translator.Translate(userLanguage, "Manage Vehicle")</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><button onclick="deleteVehicle(@Model.Id)" class="dropdown-item"><i class="bi bi-trash me-2"></i>Delete Vehicle</button></li>
|
||||
<li><button onclick="deleteVehicle(@Model.Id)" class="dropdown-item"><i class="bi bi-trash me-2"></i>@translator.Translate(userLanguage, "Delete Vehicle")</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -117,6 +134,8 @@
|
||||
<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">
|
||||
@@ -131,12 +150,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="reminderRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal fade" data-bs-focus="false" id="reminderRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="reminderRecordModalContent">
|
||||
</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};
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
@model ImportMode
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model ImportMode
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Import Data from CSV</h5>
|
||||
<h5 class="modal-title">@translator.Translate(userLanguage, "Import Data from CSV")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideBulkImportModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -9,31 +16,40 @@
|
||||
<div class="form-group">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
In order for this utility to function properly, your CSV file MUST be formatted exactly like the provided sample.
|
||||
Dates must be supplied in a string.
|
||||
Numbers must be supplied as numbers without currency formatting.
|
||||
@translator.Translate(userLanguage, "In order for this utility to function properly, your CSV file MUST be formatted exactly like the provided sample. Dates must be supplied in a string. Numbers must be supplied as numbers without currency formatting.")
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.
|
||||
@translator.Translate(userLanguage, "Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.")
|
||||
</div>
|
||||
@if (Model == ImportMode.GasRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">Download Sample</a>
|
||||
} else if (Model == ImportMode.ServiceRecord || Model == ImportMode.RepairRecord || Model == ImportMode.UpgradeRecord)
|
||||
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
||||
}
|
||||
else if (Model == ImportMode.ServiceRecord || Model == ImportMode.RepairRecord || Model == ImportMode.UpgradeRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">Download Sample</a>
|
||||
} else if (Model == ImportMode.TaxRecord)
|
||||
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
||||
}
|
||||
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/taxrecordsample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
||||
}
|
||||
else if (Model == ImportMode.SupplyRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/supplysample.csv" target="_blank">Download Sample</a>
|
||||
<a class="btn btn-link" href="/defaults/supplysample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
||||
}
|
||||
else if (Model == ImportMode.PlanRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/plansample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
||||
}
|
||||
else if (Model == ImportMode.OdometerRecord)
|
||||
{
|
||||
<a class="btn btn-link" href="/defaults/odometersample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<label for="csvFileUploader">Upload CSV File</label>
|
||||
<label for="csvFileUploader">@translator.Translate(userLanguage, "Upload CSV File")</label>
|
||||
<input onChange="uploadFileAsync(this)" type="file" multiple accept=".csv" class="form-control-file" id="csvFileUploader">
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,8 +57,8 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="hideBulkImportModal()">Cancel</button>
|
||||
<button type="button" onclick="importFromCsv()" class="btn btn-primary">Import</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideBulkImportModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
<button type="button" onclick="importFromCsv()" class="btn btn-primary">@translator.Translate(userLanguage, "Import")</button>
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFile = "";
|
||||
@@ -67,9 +83,13 @@
|
||||
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.");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
@model List<UserCollaborator>
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model List<UserCollaborator>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<span class="lead">Collaborators</span>
|
||||
<span class="lead">@translator.Translate(userLanguage, "Collaborators")</span>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button onclick="addCollaborator()" class="btn btn-link btn-sm"><i class="bi bi-person-add"></i></button>
|
||||
@@ -11,8 +18,8 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-8">Username</th>
|
||||
<th scope="col" class="col-4">Delete</th>
|
||||
<th scope="col" class="col-8">@translator.Translate(userLanguage, "Username")</th>
|
||||
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Delete")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -37,7 +44,7 @@
|
||||
if (data) {
|
||||
refreshCollaborators();
|
||||
} else {
|
||||
errorToast("An error occurred, please try again later");
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
@model CollisionRecordInput
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model CollisionRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Repair Record" : "Edit Repair Record")</h5>
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Repair Record") : translator.Translate(userLanguage,"Edit Repair Record"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddCollisionRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -12,27 +17,39 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="collisionRecordDate">Date</label>
|
||||
<label for="collisionRecordDate">@translator.Translate(userLanguage,"Date")</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="collisionRecordDate" class="form-control" placeholder="Date repair was performed" value="@Model.Date">
|
||||
<input type="text" id="collisionRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date repair was performed")" value="@Model.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="collisionRecordMileage">Odometer</label>
|
||||
<input type="number" id="collisionRecordMileage" class="form-control" placeholder="Odometer reading when repaired" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="collisionRecordDescription">Description</label>
|
||||
<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)">
|
||||
<label for="collisionRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<input type="number" id="collisionRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when repaired")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="collisionRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) repaired(i.e. Alternator)")" value="@Model.Description">
|
||||
<label for="collisionRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "RepairRecord")
|
||||
}
|
||||
<label for="collisionRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="collisionRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
{
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="collisionRecordNotes">Notes(optional)</label>
|
||||
<label for="collisionRecordNotes">@translator.Translate(userLanguage,"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())
|
||||
{
|
||||
<div>
|
||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||
<label for="collisionRecordFiles">Upload more documents</label>
|
||||
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -42,12 +59,13 @@
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
|
||||
<label class="form-check-label" for="addReminderCheck">
|
||||
Add Reminder
|
||||
@translator.Translate(userLanguage,"Add Reminder")
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<label for="collisionRecordFiles">Upload documents(optional)</label>
|
||||
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="collisionRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,20 +75,31 @@
|
||||
<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)">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Move To")</h6></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'ServiceRecord')">@translator.Translate(userLanguage,"Service Records")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'UpgradeRecord')">@translator.Translate(userLanguage,"Upgrades")</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle()">Add New Repair Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle()">@translator.Translate(userLanguage,"Add New Repair Record")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">Edit Repair Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Repair Record")</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var selectedSupplies = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
|
||||
@@ -1,33 +1,49 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model List<CollisionRecord>
|
||||
<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 Repair Records: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"Total: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Repair Records")}: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterTable('accident-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button onclick="showAddCollisionRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Repair Record</button>
|
||||
<button onclick="showAddCollisionRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Repair 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('RepairRecord')">Import via CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">Export to CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('RepairRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button onclick="showAddCollisionRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Repair Record</button>
|
||||
<button onclick="showAddCollisionRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Repair Record")</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,21 +58,21 @@
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-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">Notes</th>
|
||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage,"Date")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage,"Odometer")</th>
|
||||
<th scope="col" class="col-3 col-xl-4">@translator.Translate(userLanguage,"Description")</th>
|
||||
<th scope="col" class="col-2" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage,"Cost")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage,"Notes")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (CollisionRecord collisionRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditCollisionRecordModal(@collisionRecord.Id)">
|
||||
<td class="col-1">@collisionRecord.Date.ToShortDateString()</td>
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditCollisionRecordModal(@collisionRecord.Id)" data-tags='@string.Join(" ", collisionRecord.Tags)'>
|
||||
<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-2">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 col-xl-4">@collisionRecord.Description</td>
|
||||
<td class="col-2" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
@model CostMakeUpForVehicle
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model CostMakeUpForVehicle
|
||||
@if (Model.CollisionRecordSum + Model.ServiceRecordSum + Model.GasRecordSum + Model.TaxRecordSum + Model.UpgradeRecordSum > 0)
|
||||
{
|
||||
<canvas id="pie-chart"></canvas>
|
||||
@@ -9,17 +16,21 @@
|
||||
new Chart($("#pie-chart"), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ["Service Records", "Repairs", "Upgrades", "Tax", "Fuel"],
|
||||
labels: [decodeHTMLEntities('@translator.Translate(userLanguage, "Service Records")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Repairs")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Upgrades")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Tax")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel")')],
|
||||
datasets: [
|
||||
{
|
||||
label: "Expenses by Category",
|
||||
label: decodeHTMLEntities('@translator.Translate(userLanguage, "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')
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -34,7 +45,7 @@
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: "Expenses by Type",
|
||||
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Type")'),
|
||||
color: useDarkMode ? "#fff" : "#000"
|
||||
},
|
||||
}
|
||||
@@ -46,6 +57,6 @@
|
||||
else
|
||||
{
|
||||
<div class="text-center">
|
||||
<h4>No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.</h4>
|
||||
<h4>@translator.Translate(userLanguage, "No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.")</h4>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject IGasHelper gasHelper
|
||||
@inject ITranslationHelper translator
|
||||
@model GasRecordViewModelContainer
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
@@ -9,58 +11,82 @@
|
||||
var hideZero = userConfig.HideZero;
|
||||
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
|
||||
var gasCostFormat = useThreeDecimals ? "C3" : "C2";
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var useKwh = Model.UseKwh;
|
||||
var useHours = Model.UseHours;
|
||||
var recordTags = Model.GasRecords.SelectMany(x => x.Tags).Distinct();
|
||||
string preferredFuelEconomyUnit = userConfig.PreferredGasMileageUnit;
|
||||
string preferredGasUnit = userConfig.PreferredGasUnit;
|
||||
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">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
<span class="ms-2 badge bg-success">@($"# of Gas Records: {Model.GasRecords.Count()}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# 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">@($"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-primary" id="averageFuelMileageLabel">@($"{translator.Translate(userLanguage,"Average Fuel Economy")}: {gasHelper.GetAverageGasMileage(Model.GasRecords, useMPG)}")</span>
|
||||
if (useMPG)
|
||||
{
|
||||
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage,"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" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage,"Max Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||
} else
|
||||
{
|
||||
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage,"Min Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage,"Max Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(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(gasCostFormat)}")</span>
|
||||
}
|
||||
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage,"Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total Cost")}: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="toggleGasFilter(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Gas Record</button>
|
||||
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Gas 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('GasRecord')">Import via CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">Export to CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
} else {
|
||||
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Gas Record</button>
|
||||
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Gas Record")</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,24 +100,24 @@
|
||||
<table class="table table-hover">
|
||||
<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">@translator.Translate(userLanguage,"Date Refueled")</th>
|
||||
<th scope="col" class="col-2">@($"{translator.Translate(userLanguage,"Odometer")}({distanceUnit})")</th>
|
||||
<th scope="col" class="col-2" data-gas="consumption" data-unit="@consumptionUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{translator.Translate(userLanguage,"Consumption")}({consumptionUnit})")</th>
|
||||
<th scope="col" class="col-4" data-gas="fueleconomy" data-unit="@fuelEconomyUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{@translator.Translate(userLanguage,"Fuel Economy")}({fuelEconomyUnit})")</th>
|
||||
<th scope="col" class="col-1" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage,"Cost")</th>
|
||||
<th scope="col" class="col-1" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage,"Unit Cost")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (GasRecordViewModel gasRecord in Model.GasRecords)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditGasRecordModal(@gasRecord.Id)">
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditGasRecordModal(@gasRecord.Id)" data-tags='@string.Join(" ", gasRecord.Tags)'>
|
||||
<td class="col-2">@gasRecord.Date</td>
|
||||
<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(gasCostFormat))</td>
|
||||
<td class="col-1">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
||||
<td class="col-2" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage">@gasRecord.Mileage</td>
|
||||
<td class="col-2" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString("F")</td>
|
||||
<td class="col-4" data-gas-type="fueleconomy" data-aggregated='@(gasRecord.IncludeInAverage.ToString().ToLower())'>@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
|
||||
<td class="col-1" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
|
||||
<td class="col-1" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@@ -107,3 +133,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@if (!string.IsNullOrWhiteSpace(preferredFuelEconomyUnit))
|
||||
{
|
||||
@:convertFuelMileageUnits(decodeHTMLEntities('@fuelEconomyUnit'), decodeHTMLEntities('@preferredFuelEconomyUnit'), false);
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(preferredGasUnit))
|
||||
{
|
||||
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -1,5 +1,14 @@
|
||||
@model List<CostForVehicleByMonth>
|
||||
@if (Model.Any())
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model List<CostForVehicleByMonth>
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
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 +16,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);
|
||||
@:barGraphLabels.push(decodeHTMLEntities("@gasCost.MonthName"));
|
||||
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
|
||||
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
||||
@:barGraphColors.push('@barGraphColors[index]');
|
||||
}
|
||||
new Chart($("#bar-chart"), {
|
||||
type: 'bar',
|
||||
@@ -19,15 +32,21 @@
|
||||
labels: barGraphLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Expenses by Month",
|
||||
backgroundColor: ["#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f"],
|
||||
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Month")'),
|
||||
backgroundColor: barGraphColors,
|
||||
data: barGraphData
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
color: useDarkMode ? "#fff" : "#000",
|
||||
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Month")')
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
labels: {
|
||||
color: useDarkMode ? "#fff" : "#000"
|
||||
}
|
||||
@@ -53,6 +72,6 @@
|
||||
} else
|
||||
{
|
||||
<div class="text-center">
|
||||
<h4>No data found, insert/select some data to see visualizations here.</h4>
|
||||
<h4>@translator.Translate(userLanguage,"No data found, insert/select some data to see visualizations here.")</h4>
|
||||
</div>
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model GasRecordInputContainer
|
||||
@{
|
||||
var useMPG = config.GetUserConfig(User).UseMPG;
|
||||
var useUKMPG = config.GetUserConfig(User).UseUKMPG;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var useMPG = userConfig.UseMPG;
|
||||
var useUKMPG = userConfig.UseUKMPG;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var useKwh = Model.UseKwh;
|
||||
var useHours = Model.UseHours;
|
||||
var isNew = Model.GasRecord.Id == 0;
|
||||
string consumptionUnit;
|
||||
string distanceUnit;
|
||||
@@ -19,7 +23,11 @@
|
||||
{
|
||||
consumptionUnit = useMPG ? "gallons" : "liters";
|
||||
}
|
||||
if (useUKMPG)
|
||||
if (useHours)
|
||||
{
|
||||
distanceUnit = "hours";
|
||||
}
|
||||
else if (useUKMPG)
|
||||
{
|
||||
distanceUnit = "miles";
|
||||
}
|
||||
@@ -29,7 +37,7 @@
|
||||
}
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Gas Record" : "Edit Gas Record")</h5>
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Gas Record") : translator.Translate(userLanguage,"Edit Gas Record"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddGasRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -38,39 +46,64 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="gasRecordDate">Date</label>
|
||||
<label for="gasRecordDate">@translator.Translate(userLanguage,"Date")</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="gasRecordDate" placeholder="Date refueled" class="form-control" value="@Model.GasRecord.Date">
|
||||
<input type="text" id="gasRecordDate" placeholder="@translator.Translate(userLanguage,"Date refueled")" class="form-control" value="@Model.GasRecord.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="gasRecordMileage">Odometer Reading(@distanceUnit)</label>
|
||||
<input type="number" id="gasRecordMileage" class="form-control" placeholder="Odometer reading when refueled" value="@(isNew ? "" : Model.GasRecord.Mileage)">
|
||||
<label for="gasRecordGallons">Fuel Consumption(@(consumptionUnit))</label>
|
||||
<input type="text" id="gasRecordGallons" class="form-control" placeholder="Amount of gas refueled" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||
<label for="gasRecordMileage">@($"{translator.Translate(userLanguage,"Odometer Reading")}({distanceUnit})")</label>
|
||||
<input type="number" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when refueled")" value="@(isNew ? "" : Model.GasRecord.Mileage)">
|
||||
<label for="gasRecordGallons">@($"{translator.Translate(userLanguage, "Fuel Consumption")}({consumptionUnit})")</label>
|
||||
<input type="text" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
|
||||
<label class="form-check-label" for="gasIsFillToFull">Is Filled To Full</label>
|
||||
<label class="form-check-label" for="gasIsFillToFull">@translator.Translate(userLanguage,"Is Filled To Full")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="gasIsMissed" checked="@Model.GasRecord.MissedFuelUp">
|
||||
<label class="form-check-label" for="gasIsMissed">Missed Fuel Up(Skip MPG Calculation)</label>
|
||||
<label class="form-check-label" for="gasIsMissed">@translator.Translate(userLanguage,"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)">
|
||||
<label for="GasRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group">
|
||||
<input type="text" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Total")</option>
|
||||
<option value="unit">@translator.Translate(userLanguage,"Unit")</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<input type="text" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
}
|
||||
<label for="gasRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="gasRecordTag">
|
||||
@foreach (string tag in Model.GasRecord.Tags)
|
||||
{
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="gasRecordNotes">@translator.Translate(userLanguage,"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>
|
||||
<label for="gasRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label for="gasRecordFiles">Upload documents(optional)</label>
|
||||
<label for="gasRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="gasRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,16 +113,16 @@
|
||||
<div class="modal-footer">
|
||||
@if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-danger" onclick="deleteGasRecord(@Model.GasRecord.Id)" style="margin-right:auto;">Delete</button>
|
||||
<button type="button" class="btn btn-danger" onclick="deleteGasRecord(@Model.GasRecord.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddGasRecordModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddGasRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle()">Add New Gas Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle()">@translator.Translate(userLanguage,"Add New Gas Record")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle(true)">Edit Gas Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Gas Record")</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
@model List<CostForVehicleByMonth>
|
||||
@if (Model.Any())
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model List<CostForVehicleByMonth>
|
||||
@{
|
||||
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
||||
var sortedByMPG = Model.OrderByDescending(x => x.Cost).ToList();
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@if (Model.Where(x => x.Cost > 0).Any())
|
||||
{
|
||||
|
||||
<canvas id="bar-chart-mpg"></canvas>
|
||||
<script>
|
||||
renderChart();
|
||||
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);
|
||||
@:barGraphLabels.push(decodeHTMLEntities("@gasCost.MonthName"));
|
||||
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
|
||||
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
||||
@:barGraphColors.push('@barGraphColors[index]');
|
||||
}
|
||||
new Chart($("#bar-chart-mpg"), {
|
||||
type: 'bar',
|
||||
@@ -19,15 +33,21 @@
|
||||
labels: barGraphLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Fuel Mileage by Month",
|
||||
backgroundColor: ["#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f"],
|
||||
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel Mileage by Month")'),
|
||||
backgroundColor: barGraphColors,
|
||||
data: barGraphData
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
color: useDarkMode ? "#fff" : "#000",
|
||||
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel Mileage by Month")')
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
labels: {
|
||||
color: useDarkMode ? "#fff" : "#000"
|
||||
}
|
||||
@@ -50,9 +70,10 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center">
|
||||
<h4>No data found, insert/select some data to see visualizations here.</h4>
|
||||
<h4>@translator.Translate(userLanguage,"No data found, insert/select some data to see visualizations here.")</h4>
|
||||
</div>
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
@model Note
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model Note
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Note" : "Edit Note")</h5>
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Note") : translator.Translate(userLanguage, "Edit Note"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddNoteModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -12,13 +17,44 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="noteDescription">Description</label>
|
||||
<input type="text" id="noteDescription" class="form-control" placeholder="Description of the note" value="@(isNew ? "" : Model.Description)">
|
||||
<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">@translator.Translate(userLanguage,"Pinned")</label>
|
||||
</div>
|
||||
<label for="noteDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="noteDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of the note")" value="@(isNew ? "" : Model.Description)">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="noteTextArea">Notes</label>
|
||||
<label for="noteTextArea">@translator.Translate(userLanguage,"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 class="col-12">
|
||||
@if (Model.Files.Any())
|
||||
{
|
||||
<div>
|
||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="noteFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="noteFiles">
|
||||
<br />
|
||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="noteRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="noteRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
{
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -26,19 +62,27 @@
|
||||
<div class="modal-footer">
|
||||
@if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-danger" onclick="deleteNote(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||
<button type="button" class="btn btn-danger" onclick="deleteNote(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddNoteModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddNoteModal()">@translator.Translate(userLanguage,"Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle()">Add New Note</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle()">@translator.Translate(userLanguage,"Add New Note")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle(true)">Edit Note</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle(true)">@translator.Translate(userLanguage,"Edit Note")</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
{
|
||||
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
|
||||
}
|
||||
}
|
||||
function getNoteModelData(){
|
||||
return { id: @Model.Id}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
@model List<Note>
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model List<Note>
|
||||
@{
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<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 Notes: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Notes")}: {Model.Count()}")</span>
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterTable('notes-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Note</button>
|
||||
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Note")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,16 +37,22 @@
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-3">Description</th>
|
||||
<th scope="col" class="col-9">Note</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage,"Description")</th>
|
||||
<th scope="col" class="col-9">@translator.Translate(userLanguage,"Note")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (Note note in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)">
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)" data-tags='@string.Join(" ", note.Tags)'>
|
||||
@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>
|
||||
}
|
||||
<td class="col-9 text-truncate" data-record-type="cost">@CarCareTracker.Helper.StaticHelper.TruncateStrings(note.NoteText, 100)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
|
||||
85
Views/Vehicle/_OdometerRecordModal.cshtml
Normal file
85
Views/Vehicle/_OdometerRecordModal.cshtml
Normal file
@@ -0,0 +1,85 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model OdometerRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Odometer Record") : translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Date")</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="odometerRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date recorded")" value="@Model.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="odometerRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<input type="number" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="odometerRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="odometerRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
{
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="odometerRecordNotes">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label for="odometerRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="odometerRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"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;">@translator.Translate(userLanguage,"Delete")</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddOdometerRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle()">@translator.Translate(userLanguage,"Add New Odometer Record")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle(true)">@translator.Translate(userLanguage,"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>
|
||||
85
Views/Vehicle/_OdometerRecords.cshtml
Normal file
85
Views/Vehicle/_OdometerRecords.cshtml
Normal file
@@ -0,0 +1,85 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@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" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Odometer Records")}: {Model.Count()}")</span>
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterTable('odometer-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</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>@translator.Translate(userLanguage,"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')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('OdometerRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"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>@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Date")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage,"Odometer")</th>
|
||||
<th scope="col" class="col-7 col-xl-8">@translator.Translate(userLanguage,"Notes")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (OdometerRecord odometerRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditOdometerRecordModal(@odometerRecord.Id)" data-tags='@string.Join(" ", odometerRecord.Tags)'>
|
||||
<td class="col-2 col-xl-1">@odometerRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-3" data-record-type="cost">@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>
|
||||
108
Views/Vehicle/_PlanRecordModal.cshtml
Normal file
108
Views/Vehicle/_PlanRecordModal.cshtml
Normal file
@@ -0,0 +1,108 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model PlanRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Plan Record") : translator.Translate(userLanguage, "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">@translator.Translate(userLanguage, "Description")</label>
|
||||
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
|
||||
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "PlanRecord")
|
||||
}
|
||||
<label for="planRecordType">@translator.Translate(userLanguage, "Type")</label>
|
||||
<select class="form-select" id="planRecordType">
|
||||
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Service")</!option>
|
||||
<!option value="RepairRecord" @(Model.ImportMode == ImportMode.RepairRecord ? "selected" : "")>@translator.Translate(userLanguage, "Repair")</!option>
|
||||
<!option value="UpgradeRecord" @(Model.ImportMode == ImportMode.UpgradeRecord ? "selected" : "")>@translator.Translate(userLanguage, "Upgrade")</!option>
|
||||
</select>
|
||||
<label for="planRecordPriority">@translator.Translate(userLanguage, "Priority")</label>
|
||||
<select class="form-select" id="planRecordPriority">
|
||||
<!option value="Critical" @(Model.Priority == PlanPriority.Critical ? "selected" : "")>@translator.Translate(userLanguage, "Critical")</!option>
|
||||
<!option value="Normal" @(Model.Priority == PlanPriority.Normal || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Normal")</!option>
|
||||
<!option value="Low" @(Model.Priority == PlanPriority.Low ? "selected" : "")>@translator.Translate(userLanguage, "Low")</!option>
|
||||
</select>
|
||||
<label for="planRecordProgress">@translator.Translate(userLanguage, "Current Stage")</label>
|
||||
<select class="form-select" id="planRecordProgress">
|
||||
<!option value = "Backlog" @(Model.Progress == PlanProgress.Backlog || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Planned")</!option>
|
||||
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
|
||||
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
|
||||
</select>
|
||||
@if (!isNew)
|
||||
{
|
||||
<label>@($"{translator.Translate(userLanguage, "Date Created")}: {Model.DateCreated}")</label>
|
||||
<label>@($"{translator.Translate(userLanguage, "Last Modified")}: {Model.DateModified}")</label>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="planRecordNotes">@translator.Translate(userLanguage, "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">@translator.Translate(userLanguage, "Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="planRecordFiles">
|
||||
<br />
|
||||
|
||||
<small class="text-body-secondary">@translator.Translate(userLanguage, "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;">@translator.Translate(userLanguage, "Delete")</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle()">@translator.Translate(userLanguage, "Add New Plan Record")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle(true)">@translator.Translate(userLanguage, "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>
|
||||
103
Views/Vehicle/_PlanRecords.cshtml
Normal file
103
Views/Vehicle/_PlanRecords.cshtml
Normal file
@@ -0,0 +1,103 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
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">@($"{translator.Translate(userLanguage,"# 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>@translator.Translate(userLanguage,"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')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('PlanRecord')">@translator.Translate(userLanguage,"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>@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"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>
|
||||
@@ -1,4 +1,11 @@
|
||||
@model ReminderMakeUpForVehicle
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model ReminderMakeUpForVehicle
|
||||
@if (Model.UrgentCount + Model.VeryUrgentCount + Model.NotUrgentCount + Model.PastDueCount > 0)
|
||||
{
|
||||
<canvas id="donut-chart"></canvas>
|
||||
@@ -9,10 +16,15 @@
|
||||
new Chart($("#donut-chart"), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ["Not Urgent", "Urgent", "Very Urgent", "Past Due"],
|
||||
labels: [
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Not Urgent")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Urgent")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Very Urgent")'),
|
||||
decodeHTMLEntities('@translator.Translate(userLanguage, "Past Due")')
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: "Reminders by Category",
|
||||
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Reminders by Category")'),
|
||||
backgroundColor: ["#488f31", "#ffa600", "#de425b", "#cccccc"],
|
||||
data: [
|
||||
@Model.NotUrgentCount,
|
||||
@@ -33,7 +45,7 @@
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: "Reminders by Urgency",
|
||||
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Reminders by Urgency")'),
|
||||
color: useDarkMode ? "#fff" : "#000"
|
||||
},
|
||||
}
|
||||
@@ -45,6 +57,6 @@
|
||||
else
|
||||
{
|
||||
<div class="text-center">
|
||||
<h4>No data found, create reminders to see visualizations here.</h4>
|
||||
<h4>@translator.Translate(userLanguage,"No data found, create reminders to see visualizations here.")</h4>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
@model ReminderRecordInput
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model ReminderRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Reminder" : "Edit Reminder")</h5>
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Reminder") : translator.Translate(userLanguage, "Edit Reminder"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddReminderRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -12,35 +17,69 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12" id="reminderOptions">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="reminderDescription">Description</label>
|
||||
<input type="text" id="reminderDescription" class="form-control" placeholder="Reminder Description" value="@Model.Description">
|
||||
<label>Remind me on:</label>
|
||||
<label for="reminderDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="reminderDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Reminder Description")" value="@Model.Description">
|
||||
<label>@translator.Translate(userLanguage,"Remind me on")</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricDate" value="@(ReminderMetric.Date)" checked="@(Model.Metric == ReminderMetric.Date)">
|
||||
<label class="form-check-label" for="reminderMetricDate">Date</label>
|
||||
<label class="form-check-label" for="reminderMetricDate">@translator.Translate(userLanguage,"Date")</label>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" id="reminderDate" class="form-control" placeholder="Future Date" value="@Model.Date">
|
||||
<input type="text" id="reminderDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Future Date")" value="@Model.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricOdometer" value="@(ReminderMetric.Odometer)" checked="@(Model.Metric == ReminderMetric.Odometer)">
|
||||
<label class="form-check-label" for="reminderMetricOdometer">Odometer</label>
|
||||
<label class="form-check-label" for="reminderMetricOdometer">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="number" id="reminderMileage" class="form-control" placeholder="Future Odometer Reading" value="@(isNew ? "" : Model.Mileage)">
|
||||
<input type="number" id="reminderMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Future Odometer Reading")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="appendMileageToOdometer(500)">+500</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricBoth" value="@(ReminderMetric.Both)" checked="@(Model.Metric == ReminderMetric.Both)">
|
||||
<label class="form-check-label" for="reminderMetricBoth">Whichever comes first</label>
|
||||
<label class="form-check-label" for="reminderMetricBoth">@translator.Translate(userLanguage,"Whichever comes first")</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="reminderNotes">Notes(optional)</label>
|
||||
<label for="reminderNotes">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Is Recurring")</label>
|
||||
</div>
|
||||
<label for="reminderRecurringMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<select class="form-select" onchange="checkCustomMileageInterval()" id="reminderRecurringMileage" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<!option value="Other" @(Model.ReminderMileageInterval == ReminderMileageInterval.Other ? "selected" : "")>@(Model.ReminderMileageInterval == ReminderMileageInterval.Other && Model.CustomMileageInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMileageInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
|
||||
<!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" : "")>@translator.Translate(userLanguage,"3 Months")</!option>
|
||||
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage,"6 Months")</!option>
|
||||
<!option value="OneYear" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneYear ? "selected" : "")>@translator.Translate(userLanguage, "1 Year")</!option>
|
||||
<!option value="TwoYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.TwoYears ? "selected" : "")>@translator.Translate(userLanguage, "2 Years")</!option>
|
||||
<!option value="ThreeYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeYears ? "selected" : "")>@translator.Translate(userLanguage, "3 Years")</!option>
|
||||
<!option value="FiveYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>@translator.Translate(userLanguage, "5 Years")</!option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,20 +88,21 @@
|
||||
<div class="modal-footer">
|
||||
@if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddReminderRecordModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddReminderRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveReminderRecordToVehicle()">Add New Reminder</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveReminderRecordToVehicle()">@translator.Translate(userLanguage, "Add New Reminder")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveReminderRecordToVehicle(true)">Edit Reminder</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveReminderRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Reminder")</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
var customMileageInterval = @Model.CustomMileageInterval;
|
||||
function getReminderRecordModelData() {
|
||||
return { id: @Model.Id}
|
||||
return { id: @Model.Id, mileageInterval: '@Model.ReminderMileageInterval.ToString()'}
|
||||
}
|
||||
</script>
|
||||
@@ -1,15 +1,23 @@
|
||||
@model List<ReminderRecordViewModel>
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model List<ReminderRecordViewModel>
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
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">
|
||||
<span class="ms-2 badge bg-success">@($"# of Reminders: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-secondary">@($"Past Due: {Model.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()}")</span>
|
||||
<span class="ms-2 badge bg-danger">@($"Very Urgent: {Model.Where(x=>x.Urgency == ReminderUrgency.VeryUrgent).Count()}")</span>
|
||||
<span class="ms-2 badge bg-warning">@($"Urgent: {Model.Where(x => x.Urgency == ReminderUrgency.Urgent).Count()}")</span>
|
||||
<span class="ms-2 badge bg-success">@($"Not Urgent: {Model.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count()}")</span>
|
||||
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage, "# of Reminders")}: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-secondary">@($"{translator.Translate(userLanguage, "Past Due")}: {Model.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()}")</span>
|
||||
<span class="ms-2 badge bg-danger">@($"{translator.Translate(userLanguage, "Very Urgent")}: {Model.Where(x=>x.Urgency == ReminderUrgency.VeryUrgent).Count()}")</span>
|
||||
<span class="ms-2 badge bg-warning">@($"{translator.Translate(userLanguage, "Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.Urgent).Count()}")</span>
|
||||
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage, "Not Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count()}")</span>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Reminder</button>
|
||||
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Reminder")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,11 +31,15 @@
|
||||
<table class="table table-hover">
|
||||
<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="col-3">Notes</th>
|
||||
<th scope="col" class="col-1">Delete</th>
|
||||
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Urgency")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Metric")</th>
|
||||
<th scope="col" class="@(hasRefresh ? "col-4" : "col-5")">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Notes")</th>
|
||||
@if (hasRefresh)
|
||||
{
|
||||
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Done")</th>
|
||||
}
|
||||
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Delete")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -36,19 +48,19 @@
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditReminderRecordModal(@reminderRecord.Id)">
|
||||
@if (reminderRecord.Urgency == ReminderUrgency.VeryUrgent)
|
||||
{
|
||||
<td class="col-1"><span class="badge text-bg-danger">Very Urgent</span></td>
|
||||
<td class="col-1"><span class="badge text-bg-danger">@translator.Translate(userLanguage, "Very Urgent")</span></td>
|
||||
}
|
||||
else if (reminderRecord.Urgency == ReminderUrgency.Urgent)
|
||||
{
|
||||
<td class="col-1"><span class="badge text-bg-warning">Urgent</span></td>
|
||||
<td class="col-1"><span class="badge text-bg-warning">@translator.Translate(userLanguage, "Urgent")</span></td>
|
||||
}
|
||||
else if (reminderRecord.Urgency == ReminderUrgency.PastDue)
|
||||
{
|
||||
<td class="col-1"><span class="badge text-bg-secondary">Past Due</span></td>
|
||||
<td class="col-1"><span class="badge text-bg-secondary">@translator.Translate(userLanguage, "Past Due")</span></td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="col-1"><span class="badge text-bg-success">Not Urgent</span></td>
|
||||
<td class="col-1"><span class="badge text-bg-success">@translator.Translate(userLanguage, "Not Urgent")</span></td>
|
||||
}
|
||||
@if (reminderRecord.Metric == ReminderMetric.Date)
|
||||
{
|
||||
@@ -62,8 +74,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,5 +1,13 @@
|
||||
@model ReportViewModel
|
||||
<div class="row hideOnPrint">
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model ReportViewModel
|
||||
<div class="container reportTabContainer">
|
||||
<div class="row hideOnPrint">
|
||||
<div class="col-md-3 col-12 mt-2">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@@ -20,28 +28,30 @@
|
||||
</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="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(this)" type="checkbox" id="serviceExpenseCheck" value="1" checked>
|
||||
<label class="form-check-label" for="serviceExpenseCheck">Service</label>
|
||||
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="serviceExpenseCheck" value="1" checked>
|
||||
<label class="form-check-label" for="serviceExpenseCheck">@translator.Translate(userLanguage,"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>
|
||||
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="repairExpenseCheck" value="2" checked>
|
||||
<label class="form-check-label" for="repairExpenseCheck">@translator.Translate(userLanguage, "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>
|
||||
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="upgradeExpenseCheck" value="3" checked>
|
||||
<label class="form-check-label" for="upgradeExpenseCheck">@translator.Translate(userLanguage, "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>
|
||||
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="gasExpenseCheck" value="4" checked>
|
||||
<label class="form-check-label" for="gasExpenseCheck">@translator.Translate(userLanguage, "Fuel")</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>
|
||||
<input class="form-check-input" onChange="updateCheck()" type="checkbox" id="taxExpenseCheck" value="5" checked>
|
||||
<label class="form-check-label" for="taxExpenseCheck">@translator.Translate(userLanguage, "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">
|
||||
@@ -53,10 +63,10 @@
|
||||
<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>
|
||||
<option value="0">@translator.Translate(userLanguage, "As of Today")</option>
|
||||
<option value="30">@translator.Translate(userLanguage, "+30 Days")</option>
|
||||
<option value="60">@translator.Translate(userLanguage, "+60 Days")</option>
|
||||
<option value="90">@translator.Translate(userLanguage, "+90 Days")</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,9 +76,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row hideOnPrint">
|
||||
</div>
|
||||
<hr />
|
||||
<div class="row hideOnPrint">
|
||||
<div class="col-md-3 col-12 chartContainer" id="collaboratorContent">
|
||||
@await Html.PartialAsync("_Collaborators", Model.Collaborators)
|
||||
</div>
|
||||
@@ -78,8 +88,12 @@
|
||||
</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 class="d-grid">
|
||||
<button onclick="generateVehicleHistoryReport()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "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">@translator.Translate(userLanguage, "Export Attachments")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
@model ServiceRecordInput
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model ServiceRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Service Record" : "Edit Service Record")</h5>
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Service Record") : translator.Translate(userLanguage,"Edit Service Record"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddServiceRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -12,27 +17,39 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="serviceRecordDate">Date</label>
|
||||
<label for="serviceRecordDate">@translator.Translate(userLanguage,"Date")</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="serviceRecordDate" class="form-control" placeholder="Date service was performed" value="@Model.Date">
|
||||
<input type="text" id="serviceRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date service was performed")" value="@Model.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="serviceRecordMileage">Odometer</label>
|
||||
<input type="number" id="serviceRecordMileage" class="form-control" placeholder="Odometer reading when serviced" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="serviceRecordDescription">Description</label>
|
||||
<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)">
|
||||
<label for="serviceRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
|
||||
<input type="number" id="serviceRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when serviced")" value="@(isNew ? "" : Model.Mileage)">
|
||||
<label for="serviceRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="serviceRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) serviced(i.e. Oil Change)")" value="@Model.Description">
|
||||
<label for="serviceRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="serviceRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the service")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "ServiceRecord")
|
||||
}
|
||||
<label for="serviceRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="serviceRecordTag">
|
||||
@foreach(string tag in Model.Tags)
|
||||
{
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="serviceRecordNotes">Notes(optional)</label>
|
||||
<label for="serviceRecordNotes">@translator.Translate(userLanguage,"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())
|
||||
{
|
||||
<div>
|
||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||
<label for="serviceRecordFiles">Upload more documents</label>
|
||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -42,12 +59,13 @@
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
|
||||
<label class="form-check-label" for="addReminderCheck">
|
||||
Add Reminder
|
||||
@translator.Translate(userLanguage,"Add Reminder")
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<label for="serviceRecordFiles">Upload documents(optional)</label>
|
||||
<label for="serviceRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,20 +75,31 @@
|
||||
<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)">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Move To")</h6></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'ServiceRecord', 'RepairRecord')">@translator.Translate(userLanguage,"Repairs")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'ServiceRecord', 'UpgradeRecord')">@translator.Translate(userLanguage,"Upgrades")</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddServiceRecordModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddServiceRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveServiceRecordToVehicle()">Add New Service Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveServiceRecordToVehicle()">@translator.Translate(userLanguage,"Add New Service Record")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveServiceRecordToVehicle(true)">Edit Service Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveServiceRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Service Record")</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
var uploadedFiles = [];
|
||||
var selectedSupplies = [];
|
||||
getUploadedFilesFromModel();
|
||||
function getUploadedFilesFromModel() {
|
||||
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||
|
||||
@@ -1,33 +1,49 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
}
|
||||
@model List<ServiceRecord>
|
||||
<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 Service Records: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"Total: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Service Records")}: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
@foreach(string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterTable('servicerecord-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Service Record</button>
|
||||
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Service 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('ServiceRecord')">Import via CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('ServiceRecord')">Export to CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('ServiceRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('ServiceRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Service Record</button>
|
||||
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Service Record")</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,21 +58,21 @@
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-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">Notes</th>
|
||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage,"Date")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Odometer")</th>
|
||||
<th scope="col" class="col-3 col-xl-4">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Notes")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (ServiceRecord serviceRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditServiceRecordModal(@serviceRecord.Id)">
|
||||
<td class="col-1">@serviceRecord.Date.ToShortDateString()</td>
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditServiceRecordModal(@serviceRecord.Id)" data-tags='@string.Join(" ",serviceRecord.Tags)'>
|
||||
<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-2">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 col-xl-4">@serviceRecord.Description</td>
|
||||
<td class="col-2" data-record-type="cost">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
@model SupplyRecordInput
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model SupplyRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Supply Record" : "Edit Supply Record")</h5>
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Supply Record") : translator.Translate(userLanguage,"Edit Supply Record"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddSupplyRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -12,43 +17,45 @@
|
||||
<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>
|
||||
<label for="supplyRecordDate">@translator.Translate(userLanguage,"Date")</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="supplyRecordDate" class="form-control" placeholder="Date purchased" value="@Model.Date">
|
||||
<input type="text" id="supplyRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"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">
|
||||
<label for="supplyRecordPartNumber">@translator.Translate(userLanguage,"Part Number")</label>
|
||||
<input type="text" id="supplyRecordPartNumber" class="form-control" placeholder="@translator.Translate(userLanguage,"Part #/Model #/SKU #")" value="@(isNew ? "" : Model.PartNumber)">
|
||||
<label for="supplyRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="supplyRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of the Part/Supplies")" value="@Model.Description">
|
||||
<label for="supplyRecordSupplier">@translator.Translate(userLanguage,"Supplier/Vendor")</label>
|
||||
<input type="text" id="supplyRecordSupplier" class="form-control" placeholder="@translator.Translate(userLanguage,"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)">
|
||||
<label for="supplyRecordQuantity">@translator.Translate(userLanguage,"Quantity")</label>
|
||||
<input type="text" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"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)">
|
||||
<label for="supplyRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="supplyRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost")" value="@(isNew ? "" : Model.Cost)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordNotes">Notes(optional)</label>
|
||||
<label for="supplyRecordNotes">@translator.Translate(userLanguage,"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>
|
||||
<label for="supplyRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label for="supplyRecordFiles">Upload documents(optional)</label>
|
||||
<label for="supplyRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,16 +65,16 @@
|
||||
<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-danger" onclick="deleteSupplyRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddSupplyRecordModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddSupplyRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle()">Add New Supply Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle()">@translator.Translate(userLanguage,"Add New Supply Record")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle(true)">Edit Supply Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Supply Record")</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
|
||||
@@ -1,33 +1,38 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var hideZero = userConfig.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>
|
||||
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage,"# of Supply Records")}: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"{translator.Translate(userLanguage,"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 onclick="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"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><a class="dropdown-item" href="#" onclick="showBulkImportModal('SupplyRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('SupplyRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"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>
|
||||
<button onclick="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Supply Record")</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,23 +47,23 @@
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1">Date</th>
|
||||
<th scope="col" class="col-2">Part #</th>
|
||||
<th scope="col" class="col-2">Supplier</th>
|
||||
<th scope="col" class="col-3">Description</th>
|
||||
<th scope="col" class="col-1">Quantity</th>
|
||||
<th scope="col" class="col-1">Cost</th>
|
||||
<th scope="col" class="col-2">Notes</th>
|
||||
<th scope="col" class="col-2 col-xl-1">@translator.Translate(userLanguage,"Date")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part #")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Supplier")</th>
|
||||
<th scope="col" class="col-2 col-xl-3">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-1" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Quantity")</th>
|
||||
<th scope="col" class="col-1" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Notes")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (SupplyRecord supplyRecord in Model)
|
||||
{
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditSupplyRecordModal(@supplyRecord.Id)">
|
||||
<td class="col-1">@supplyRecord.Date.ToShortDateString()</td>
|
||||
<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-3">@supplyRecord.Description</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>
|
||||
|
||||
98
Views/Vehicle/_SupplyStore.cshtml
Normal file
98
Views/Vehicle/_SupplyStore.cshtml
Normal file
@@ -0,0 +1,98 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model string
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<a onclick="showSuppliesModal()" class="btn btn-link">@translator.Translate(userLanguage,"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>
|
||||
133
Views/Vehicle/_SupplyUsage.cshtml
Normal file
133
Views/Vehicle/_SupplyUsage.cshtml
Normal file
@@ -0,0 +1,133 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model List<SupplyRecord>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@translator.Translate(userLanguage,"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">
|
||||
@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Quantity")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "In Stock")</th>
|
||||
<th scope="col" class="col-5">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "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>@translator.Translate(userLanguage, "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()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
<button type="button" class="btn btn-primary" disabled id="selectSuppliesButton" onclick="selectSupplies()">@translator.Translate(userLanguage, "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>
|
||||
@@ -1,9 +1,14 @@
|
||||
@model TaxRecordInput
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model TaxRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isNew ? "Add New Tax Record" : "Edit Tax Record")</h5>
|
||||
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Tax Record") : translator.Translate(userLanguage,"Edit Tax Record"))</h5>
|
||||
<button type="button" class="btn-close" onclick="hideAddTaxRecordModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -12,25 +17,47 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||
<label for="taxRecordDate">Date</label>
|
||||
<label for="taxRecordDate">@translator.Translate(userLanguage,"Date")</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="taxRecordDate" class="form-control" placeholder="Date tax was paid" value="@Model.Date">
|
||||
<input type="text" id="taxRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date tax was paid")" value="@Model.Date">
|
||||
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||
</div>
|
||||
<label for="taxRecordDescription">Description</label>
|
||||
<input type="text" id="taxRecordDescription" class="form-control" placeholder="Description of tax paid(i.e. Registration)" value="@Model.Description">
|
||||
<label for="taxRecordCost">Cost</label>
|
||||
<input type="text" id="taxRecordCost" class="form-control" placeholder="Cost of tax paid" value="@(isNew? "" : Model.Cost)">
|
||||
<label for="taxRecordDescription">@translator.Translate(userLanguage,"Description")</label>
|
||||
<input type="text" id="taxRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of tax paid(i.e. Registration)")" value="@Model.Description">
|
||||
<label for="taxRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" id="taxRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of tax paid")" value="@(isNew? "" : Model.Cost)">
|
||||
<label for="taxRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="taxRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
{
|
||||
<!option value="@tag">@tag</!option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="taxRecordNotes">Notes(optional)</label>
|
||||
<label for="taxRecordNotes">@translator.Translate(userLanguage,"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">@translator.Translate(userLanguage,"Is Recurring")</label>
|
||||
</div>
|
||||
<label for="taxRecurringMonth">@translator.Translate(userLanguage,"Month")</label>
|
||||
<select class="form-select" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
|
||||
<!option value="OneMonth" @(Model.RecurringInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage,"1 Month")</!option>
|
||||
<!option value="ThreeMonths" @(Model.RecurringInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage, "3 Months")</!option>
|
||||
<!option value="SixMonths" @(Model.RecurringInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage, "6 Months")</!option>
|
||||
<!option value="OneYear" @(Model.RecurringInterval == ReminderMonthInterval.OneYear ? "selected" : "")>@translator.Translate(userLanguage, "1 Year")</!option>
|
||||
<!option value="TwoYears" @(Model.RecurringInterval == ReminderMonthInterval.TwoYears ? "selected" : "")>@translator.Translate(userLanguage, "2 Years")</!option>
|
||||
<!option value="ThreeYears" @(Model.RecurringInterval == ReminderMonthInterval.ThreeYears ? "selected" : "")>@translator.Translate(userLanguage, "3 Years")</!option>
|
||||
<!option value="FiveYears" @(Model.RecurringInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>@translator.Translate(userLanguage, "5 Years")</!option>
|
||||
</select>
|
||||
@if (Model.Files.Any())
|
||||
{
|
||||
<div>
|
||||
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||
<label for="taxRecordFiles">Upload more documents</label>
|
||||
<label for="taxRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="taxRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -40,12 +67,13 @@
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
|
||||
<label class="form-check-label" for="addReminderCheck">
|
||||
Add Reminder
|
||||
@translator.Translate(userLanguage,"Add Reminder")
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<label for="taxRecordFiles">Upload documents(optional)</label>
|
||||
<label for="taxRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="taxRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,16 +83,16 @@
|
||||
<div class="modal-footer">
|
||||
@if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-danger" onclick="deleteTaxRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||
<button type="button" class="btn btn-danger" onclick="deleteTaxRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddTaxRecordModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideAddTaxRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
|
||||
@if (isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveTaxRecordToVehicle()">Add New Tax Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveTaxRecordToVehicle()">@translator.Translate(userLanguage,"Add New Tax Record")</button>
|
||||
}
|
||||
else if (!isNew)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" onclick="saveTaxRecordToVehicle(true)">Edit Tax Record</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveTaxRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Tax Record")</button>
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
|
||||
@@ -1,33 +1,49 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
|
||||
var hideZero = config.GetUserConfig(User).HideZero;
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var enableCsvImports = userConfig.EnableCsvImports;
|
||||
var hideZero = userConfig.HideZero;
|
||||
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
|
||||
}
|
||||
@model List<TaxRecord>
|
||||
<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 Tax Records: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary">@($"Total: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Tax Records")}: {Model.Count()}")</span>
|
||||
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<span onclick="filterTable('tax-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
|
||||
}
|
||||
<datalist id="tagList">
|
||||
@foreach (string recordTag in recordTags)
|
||||
{
|
||||
<!option value="@recordTag"></!option>
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
@if (enableCsvImports)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button onclick="showAddTaxRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Tax Record</button>
|
||||
<button onclick="showAddTaxRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Tax 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('TaxRecord')">Import via CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('TaxRecord')">Export to CSV</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('TaxRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('TaxRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button onclick="showAddTaxRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Tax Record</button>
|
||||
<button onclick="showAddTaxRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Tax Record")</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,19 +58,19 @@
|
||||
<table class="table table-hover">
|
||||
<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">Notes</th>
|
||||
<th scope="col" class="col-3 col-xl-1">@translator.Translate(userLanguage, "Date")</th>
|
||||
<th scope="col" class="col-4 col-xl-6">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2" onclick="toggleSort('tax-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Notes")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@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-2">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditTaxRecordModal(@taxRecord.Id)" data-tags='@string.Join(" ", taxRecord.Tags)'>
|
||||
<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" data-record-type="cost">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(taxRecord.Notes)</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user