Compare commits
134 Commits
v1.3.8
...
Hargata/de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f1b585893 | ||
|
|
92c0c103be | ||
|
|
c5a5de50a7 | ||
|
|
7715e7b4ab | ||
|
|
5a3ed3d3d4 | ||
|
|
c1e2394194 | ||
|
|
6abb569df1 | ||
|
|
e59e33bc3b | ||
|
|
a671dc1937 | ||
|
|
426b52aee4 | ||
|
|
87fe011565 | ||
|
|
30c03af5b0 | ||
|
|
c9c8fc8705 | ||
|
|
9cd8030808 | ||
|
|
5411d4152c | ||
|
|
c5a05afe1c | ||
|
|
5b44f2ae33 | ||
|
|
039b30da4f | ||
|
|
df45378bce | ||
|
|
3729d2c23f | ||
|
|
89ece413c8 | ||
|
|
c1b361397f | ||
|
|
ada0715343 | ||
|
|
5cdc687f6d | ||
|
|
48f0e16fde | ||
|
|
d6b6600ce9 | ||
|
|
82634e9e1b | ||
|
|
225e5e58bc | ||
|
|
21d135a8ab | ||
|
|
7a0636a48a | ||
|
|
0b9ca77281 | ||
|
|
feaf631b47 | ||
|
|
ec24c19821 | ||
|
|
36a120fa0f | ||
|
|
fb169d5054 | ||
|
|
59fa7b6a1d | ||
|
|
201d832b92 | ||
|
|
31b4ef5458 | ||
|
|
b06ad0cdaa | ||
|
|
f3914bc3b7 | ||
|
|
aea870974b | ||
|
|
7f6265ba05 | ||
|
|
85542cb278 | ||
|
|
f6e0873d83 | ||
|
|
745b262dfc | ||
|
|
73df755f70 | ||
|
|
762a1b98d3 | ||
|
|
571e87dd14 | ||
|
|
22eb38709a | ||
|
|
986a8420f8 | ||
|
|
cff5f8e8e9 | ||
|
|
0df6cea88c | ||
|
|
88ff2432f6 | ||
|
|
efb7fb999b | ||
|
|
1a2b503791 | ||
|
|
e68d4ffa3a | ||
|
|
d701a8964f | ||
|
|
b6929113bc | ||
|
|
fc1eac1049 | ||
|
|
189659ff1e | ||
|
|
cb19130119 | ||
|
|
1596bf1deb | ||
|
|
636cbb3d21 | ||
|
|
cff7845dd4 | ||
|
|
2d9cc3507f | ||
|
|
4c6ac96dd1 | ||
|
|
e5a594367e | ||
|
|
12aaf8c209 | ||
|
|
cafbb156af | ||
|
|
36ac61d848 | ||
|
|
df066a593e | ||
|
|
7ac9185e4c | ||
|
|
090cbba1a0 | ||
|
|
5e3529f9cc | ||
|
|
f90ddeabb4 | ||
|
|
f54419b993 | ||
|
|
cbbd7ff25f | ||
|
|
0c2c089828 | ||
|
|
6a1813399f | ||
|
|
4c8d2b3704 | ||
|
|
23f97ebd46 | ||
|
|
e3dccf0182 | ||
|
|
272596878c | ||
|
|
46953e9063 | ||
|
|
30f35855f2 | ||
|
|
df94132917 | ||
|
|
de37d689be | ||
|
|
4f941aad52 | ||
|
|
2febdf1259 | ||
|
|
349808a609 | ||
|
|
7d58d1bc40 | ||
|
|
5e91a26230 | ||
|
|
0942ae1742 | ||
|
|
cce9bd29e5 | ||
|
|
05fdeef862 | ||
|
|
dfa08b287c | ||
|
|
77920f94cd | ||
|
|
3631a036c6 | ||
|
|
1445959d05 | ||
|
|
0b45de5ba8 | ||
|
|
d86cdfcefd | ||
|
|
0c7aa304f7 | ||
|
|
e5114f491d | ||
|
|
e238bd8a12 | ||
|
|
f53ad74c72 | ||
|
|
3d456d1cd5 | ||
|
|
de32fe4d85 | ||
|
|
29a90fe814 | ||
|
|
3f31f091dc | ||
|
|
571b558181 | ||
|
|
ed0775ab71 | ||
|
|
19e19d7c15 | ||
|
|
b7e574889c | ||
|
|
2a66c97254 | ||
|
|
e14ef961b9 | ||
|
|
ec2c17be23 | ||
|
|
b5162d2044 | ||
|
|
ae428b94d1 | ||
|
|
3350f3c39a | ||
|
|
68caf82167 | ||
|
|
8c5f1c3a01 | ||
|
|
e1bbc232e1 | ||
|
|
fbc7a39996 | ||
|
|
1ce77ad1ee | ||
|
|
08cee8029f | ||
|
|
e33bcf5e57 | ||
|
|
cf5ceaa959 | ||
|
|
8293bed89c | ||
|
|
a830580c27 | ||
|
|
94c6cca25c | ||
|
|
78e0a0146c | ||
|
|
ea667ed950 | ||
|
|
f8b23b5f20 | ||
|
|
4dc896084e |
6
.env
6
.env
@@ -7,6 +7,6 @@ MailConfig__Username=""
|
|||||||
MailConfig__Password=""
|
MailConfig__Password=""
|
||||||
LOGGING__LOGLEVEL__DEFAULT=Error
|
LOGGING__LOGLEVEL__DEFAULT=Error
|
||||||
|
|
||||||
# * Uncoment this line if you use postgresSQL as database backend.
|
# This file is provided as a GUIDELINE ONLY
|
||||||
# * Check the docker-compose.postgresql.yml file
|
# Use the LubeLogger Configurator to configure your environment variables
|
||||||
#POSTGRES_CONNECTION="Host=postgres;Username=lubelogger;Password=lubepass;Database=lubelogger;"
|
# https://lubelogger.com/configure
|
||||||
48
.github/CONTRIBUTING.md
vendored
Normal file
48
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
## Asking Questions
|
||||||
|
Before submitting a question, please check the [Troubleshooting Guide](https://docs.lubelogger.com/Installation/Troubleshooting) and search through existing issues.
|
||||||
|
|
||||||
|
Ideally, the Issues tab should only consist of bug reports and feature requests instead of debugging your LubeLogger installation.
|
||||||
|
|
||||||
|
## Feature Requests
|
||||||
|
Feature Requests are cool, but we do want to avoid bloat and scope/feature creep.
|
||||||
|
|
||||||
|
LubeLogger is a Vehicle Maintenance and Fuel Mileage Tracker.
|
||||||
|
It is not and should not be used for the following:
|
||||||
|
- Project Management Software(e.g.: Jira)
|
||||||
|
- Budget Management(e.g: YNAB/Actual)
|
||||||
|
- Inventory Management
|
||||||
|
|
||||||
|
Before submitting a feature request, please consider the following:
|
||||||
|
- Will this feature benefit more than just a niche subset of users
|
||||||
|
- Will this feature result in scope creep
|
||||||
|
- Has this feature already been requested/exist
|
||||||
|
|
||||||
|
## Bug Reports
|
||||||
|
Please fill out the issue template for bug reports.
|
||||||
|
|
||||||
|
## Security Vulnerabilities
|
||||||
|
Contact us via [Email](mailto:hargatasoftworks@gmail.com)
|
||||||
|
|
||||||
|
## Submitting Pull Requests
|
||||||
|
Pull Requests are welcome, but please consider the following:
|
||||||
|
|
||||||
|
CI/CD, Bugs, Tech-Debt Related PRs:
|
||||||
|
- Make sure changes are not breaking.
|
||||||
|
- If any dependencies are added, they must be cross-platform compatible.
|
||||||
|
- Please test changes thoroughly.
|
||||||
|
|
||||||
|
Feature Related PRs:
|
||||||
|
- Have you requested this feature in the Issues tab
|
||||||
|
- Will this feature benefit more than just a niche subset of users
|
||||||
|
- Will this feature result in scope creep
|
||||||
|
- Do these changes fix a root cause or is it simply a workaround
|
||||||
|
|
||||||
|
Ideally, you should first submit a feature request in the Issues tab before submitting a PR just in case it's something we are already working on.
|
||||||
|
|
||||||
|
Note the following:
|
||||||
|
|
||||||
|
We(the maintainers) are not responsible for cleaning up, testing, or fixing your changes.
|
||||||
|
|
||||||
|
If your changes doesn't have a wide-ranging use-case, has not been thoroughly tested, or cannot be easily maintained, we won't merge your PR.
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ config/userConfig.json
|
|||||||
CarCareTracker.csproj.user
|
CarCareTracker.csproj.user
|
||||||
Properties/launchSettings.json
|
Properties/launchSettings.json
|
||||||
data/cartracker-log.db
|
data/cartracker-log.db
|
||||||
|
data/widgets.html
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||||
<PackageReference Include="MailKit" Version="4.5.0" />
|
<PackageReference Include="MailKit" Version="4.8.0" />
|
||||||
<PackageReference Include="Npgsql" Version="8.0.3" />
|
<PackageReference Include="Npgsql" Version="8.0.5" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.8.34330.188
|
VisualStudioVersion = 17.8.34330.188
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarCareTracker", "CarCareTracker.csproj", "{0DB85611-6555-4127-A66E-DADD25A16526}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarCareTracker", "CarCareTracker.csproj", "{0DB85611-6555-4127-A66E-DADD25A16526}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
@@ -125,55 +125,7 @@ namespace CarCareTracker.Controllers
|
|||||||
vehicles.AddRange(result);
|
vehicles.AddRange(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<VehicleInfo> apiResult = new List<VehicleInfo>();
|
var apiResult = _vehicleLogic.GetVehicleInfo(vehicles);
|
||||||
|
|
||||||
foreach(Vehicle vehicle in vehicles)
|
|
||||||
{
|
|
||||||
var currentMileage = _vehicleLogic.GetMaxMileage(vehicle.Id);
|
|
||||||
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
|
||||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
|
|
||||||
|
|
||||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id);
|
|
||||||
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id);
|
|
||||||
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id);
|
|
||||||
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id);
|
|
||||||
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id);
|
|
||||||
var planRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
|
|
||||||
|
|
||||||
var resultToAdd = new VehicleInfo()
|
|
||||||
{
|
|
||||||
VehicleData = vehicle,
|
|
||||||
LastReportedOdometer = currentMileage,
|
|
||||||
ServiceRecordCount = serviceRecords.Count(),
|
|
||||||
ServiceRecordCost = serviceRecords.Sum(x=>x.Cost),
|
|
||||||
RepairRecordCount = repairRecords.Count(),
|
|
||||||
RepairRecordCost = repairRecords.Sum(x=>x.Cost),
|
|
||||||
UpgradeRecordCount = upgradeRecords.Count(),
|
|
||||||
UpgradeRecordCost = upgradeRecords.Sum(x=>x.Cost),
|
|
||||||
GasRecordCount = gasRecords.Count(),
|
|
||||||
GasRecordCost = gasRecords.Sum(x=>x.Cost),
|
|
||||||
TaxRecordCount = taxRecords.Count(),
|
|
||||||
TaxRecordCost = taxRecords.Sum(x=> x.Cost),
|
|
||||||
VeryUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.VeryUrgent),
|
|
||||||
PastDueReminderCount = results.Count(x => x.Urgency == ReminderUrgency.PastDue),
|
|
||||||
UrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.Urgent),
|
|
||||||
NotUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.NotUrgent),
|
|
||||||
PlanRecordBackLogCount = planRecords.Count(x=>x.Progress == PlanProgress.Backlog),
|
|
||||||
PlanRecordInProgressCount = planRecords.Count(x=>x.Progress == PlanProgress.InProgress),
|
|
||||||
PlanRecordTestingCount = planRecords.Count(x=>x.Progress == PlanProgress.Testing),
|
|
||||||
PlanRecordDoneCount = planRecords.Count(x=>x.Progress == PlanProgress.Done)
|
|
||||||
};
|
|
||||||
//set next reminder
|
|
||||||
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
|
|
||||||
{
|
|
||||||
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
|
||||||
}
|
|
||||||
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
|
|
||||||
{
|
|
||||||
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
|
||||||
}
|
|
||||||
apiResult.Add(resultToAdd);
|
|
||||||
}
|
|
||||||
return Json(apiResult);
|
return Json(apiResult);
|
||||||
}
|
}
|
||||||
[TypeFilter(typeof(CollaboratorFilter))]
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
@@ -187,7 +139,7 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(odometer);
|
return Json(odometer);
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
var convertedOdometer = (odometer + int.Parse(vehicle.OdometerDifference)) * int.Parse(vehicle.OdometerMultiplier);
|
var convertedOdometer = (odometer + int.Parse(vehicle.OdometerDifference)) * decimal.Parse(vehicle.OdometerMultiplier);
|
||||||
return Json(convertedOdometer);
|
return Json(convertedOdometer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +193,8 @@ namespace CarCareTracker.Controllers
|
|||||||
Description = input.Description,
|
Description = input.Description,
|
||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Cost = decimal.Parse(input.Cost),
|
Cost = decimal.Parse(input.Cost),
|
||||||
ExtraFields = input.ExtraFields
|
ExtraFields = input.ExtraFields,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
|
||||||
};
|
};
|
||||||
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
|
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
@@ -318,7 +271,8 @@ namespace CarCareTracker.Controllers
|
|||||||
Description = input.Description,
|
Description = input.Description,
|
||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Cost = decimal.Parse(input.Cost),
|
Cost = decimal.Parse(input.Cost),
|
||||||
ExtraFields = input.ExtraFields
|
ExtraFields = input.ExtraFields,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
|
||||||
};
|
};
|
||||||
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
|
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
@@ -395,7 +349,8 @@ namespace CarCareTracker.Controllers
|
|||||||
Description = input.Description,
|
Description = input.Description,
|
||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Cost = decimal.Parse(input.Cost),
|
Cost = decimal.Parse(input.Cost),
|
||||||
ExtraFields = input.ExtraFields
|
ExtraFields = input.ExtraFields,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
|
||||||
};
|
};
|
||||||
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
|
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
@@ -469,7 +424,8 @@ namespace CarCareTracker.Controllers
|
|||||||
Description = input.Description,
|
Description = input.Description,
|
||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Cost = decimal.Parse(input.Cost),
|
Cost = decimal.Parse(input.Cost),
|
||||||
ExtraFields = input.ExtraFields
|
ExtraFields = input.ExtraFields,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
|
||||||
};
|
};
|
||||||
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
|
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
|
||||||
@@ -553,7 +509,8 @@ namespace CarCareTracker.Controllers
|
|||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
InitialMileage = (string.IsNullOrWhiteSpace(input.InitialOdometer) || int.Parse(input.InitialOdometer) == default) ? _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()) : int.Parse(input.InitialOdometer),
|
InitialMileage = (string.IsNullOrWhiteSpace(input.InitialOdometer) || int.Parse(input.InitialOdometer) == default) ? _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()) : int.Parse(input.InitialOdometer),
|
||||||
Mileage = int.Parse(input.Odometer),
|
Mileage = int.Parse(input.Odometer),
|
||||||
ExtraFields = input.ExtraFields
|
ExtraFields = input.ExtraFields,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
|
||||||
};
|
};
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
|
||||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
|
||||||
@@ -634,7 +591,8 @@ namespace CarCareTracker.Controllers
|
|||||||
MissedFuelUp = bool.Parse(input.MissedFuelUp),
|
MissedFuelUp = bool.Parse(input.MissedFuelUp),
|
||||||
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
|
||||||
Cost = decimal.Parse(input.Cost),
|
Cost = decimal.Parse(input.Cost),
|
||||||
ExtraFields = input.ExtraFields
|
ExtraFields = input.ExtraFields,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
|
||||||
};
|
};
|
||||||
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
|
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
|
||||||
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
|||||||
@@ -22,16 +22,47 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
var viewModel = new AdminViewModel
|
var viewModel = new AdminViewModel
|
||||||
{
|
{
|
||||||
Users = _loginLogic.GetAllUsers(),
|
Users = _loginLogic.GetAllUsers().OrderBy(x=>x.Id).ToList(),
|
||||||
Tokens = _loginLogic.GetAllTokens()
|
Tokens = _loginLogic.GetAllTokens()
|
||||||
};
|
};
|
||||||
return View(viewModel);
|
return View(viewModel);
|
||||||
}
|
}
|
||||||
|
public IActionResult GetTokenPartialView()
|
||||||
|
{
|
||||||
|
var viewModel = _loginLogic.GetAllTokens();
|
||||||
|
return PartialView("_Tokens", viewModel);
|
||||||
|
}
|
||||||
|
public IActionResult GetUserPartialView()
|
||||||
|
{
|
||||||
|
var viewModel = _loginLogic.GetAllUsers().OrderBy(x => x.Id).ToList();
|
||||||
|
return PartialView("_Users", viewModel);
|
||||||
|
}
|
||||||
public IActionResult GenerateNewToken(string emailAddress, bool autoNotify)
|
public IActionResult GenerateNewToken(string emailAddress, bool autoNotify)
|
||||||
|
{
|
||||||
|
if (emailAddress.Contains(","))
|
||||||
|
{
|
||||||
|
string[] emailAddresses = emailAddress.Split(',');
|
||||||
|
foreach(string emailAdd in emailAddresses)
|
||||||
|
{
|
||||||
|
var trimmedEmail = emailAdd.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(trimmedEmail))
|
||||||
|
{
|
||||||
|
var result = _loginLogic.GenerateUserToken(emailAdd.Trim(), autoNotify);
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
//if fail, return prematurely
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var successResponse = new OperationResponse { Success = true, Message = "Token Generated!" };
|
||||||
|
return Json(successResponse);
|
||||||
|
} else
|
||||||
{
|
{
|
||||||
var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify);
|
var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify);
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DeleteToken(int tokenId)
|
public IActionResult DeleteToken(int tokenId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -92,5 +92,18 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return Path.Combine("/", uploadDirectory, fileName);
|
return Path.Combine("/", uploadDirectory, fileName);
|
||||||
}
|
}
|
||||||
|
public IActionResult UploadCoordinates(List<string> coordinates)
|
||||||
|
{
|
||||||
|
string uploadDirectory = "temp/";
|
||||||
|
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
||||||
|
if (!Directory.Exists(uploadPath))
|
||||||
|
Directory.CreateDirectory(uploadPath);
|
||||||
|
string fileName = Guid.NewGuid() + ".csv";
|
||||||
|
string filePath = Path.Combine(uploadPath, fileName);
|
||||||
|
string fileData = string.Join("\r\n", coordinates);
|
||||||
|
System.IO.File.WriteAllText(filePath, fileData);
|
||||||
|
var uploadedFile = new UploadedFiles { Name = "coordinates.csv", Location = Path.Combine("/", uploadDirectory, fileName) };
|
||||||
|
return Json(uploadedFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace CarCareTracker.Controllers
|
|||||||
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
|
||||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
private readonly IReminderHelper _reminderHelper;
|
private readonly IReminderHelper _reminderHelper;
|
||||||
|
private readonly ITranslationHelper _translationHelper;
|
||||||
public HomeController(ILogger<HomeController> logger,
|
public HomeController(ILogger<HomeController> logger,
|
||||||
IVehicleDataAccess dataAccess,
|
IVehicleDataAccess dataAccess,
|
||||||
IUserLogic userLogic,
|
IUserLogic userLogic,
|
||||||
@@ -31,7 +32,8 @@ namespace CarCareTracker.Controllers
|
|||||||
IFileHelper fileHelper,
|
IFileHelper fileHelper,
|
||||||
IExtraFieldDataAccess extraFieldDataAccess,
|
IExtraFieldDataAccess extraFieldDataAccess,
|
||||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
IReminderHelper reminderHelper)
|
IReminderHelper reminderHelper,
|
||||||
|
ITranslationHelper translationHelper)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dataAccess = dataAccess;
|
_dataAccess = dataAccess;
|
||||||
@@ -43,6 +45,7 @@ namespace CarCareTracker.Controllers
|
|||||||
_reminderHelper = reminderHelper;
|
_reminderHelper = reminderHelper;
|
||||||
_loginLogic = loginLogic;
|
_loginLogic = loginLogic;
|
||||||
_vehicleLogic = vehicleLogic;
|
_vehicleLogic = vehicleLogic;
|
||||||
|
_translationHelper = translationHelper;
|
||||||
}
|
}
|
||||||
private int GetUserID()
|
private int GetUserID()
|
||||||
{
|
{
|
||||||
@@ -52,6 +55,59 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
return View(model: tab);
|
return View(model: tab);
|
||||||
}
|
}
|
||||||
|
[Route("/kiosk")]
|
||||||
|
public IActionResult Kiosk(string exclusions, KioskMode kioskMode = KioskMode.Vehicle)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
var viewModel = new KioskViewModel
|
||||||
|
{
|
||||||
|
Exclusions = string.IsNullOrWhiteSpace(exclusions) ? new List<int>() : exclusions.Split(',').Select(x => int.Parse(x)).ToList(),
|
||||||
|
KioskMode = kioskMode
|
||||||
|
};
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return View(new KioskViewModel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult KioskContent(KioskViewModel kioskParameters)
|
||||||
|
{
|
||||||
|
var vehiclesStored = _dataAccess.GetVehicles();
|
||||||
|
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||||
|
}
|
||||||
|
vehiclesStored.RemoveAll(x => kioskParameters.Exclusions.Contains(x.Id));
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
if (userConfig.HideSoldVehicles)
|
||||||
|
{
|
||||||
|
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
|
||||||
|
}
|
||||||
|
switch (kioskParameters.KioskMode)
|
||||||
|
{
|
||||||
|
case KioskMode.Vehicle:
|
||||||
|
{
|
||||||
|
var kioskResult = _vehicleLogic.GetVehicleInfo(vehiclesStored);
|
||||||
|
return PartialView("_Kiosk", kioskResult);
|
||||||
|
}
|
||||||
|
case KioskMode.Plan:
|
||||||
|
{
|
||||||
|
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
|
||||||
|
return PartialView("_KioskPlan", kioskResult);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KioskMode.Reminder:
|
||||||
|
{
|
||||||
|
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);
|
||||||
|
return PartialView("_KioskReminder", kioskResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = _vehicleLogic.GetVehicleInfo(vehiclesStored);
|
||||||
|
return PartialView("_Kiosk", result);
|
||||||
|
}
|
||||||
public IActionResult Garage()
|
public IActionResult Garage()
|
||||||
{
|
{
|
||||||
var vehiclesStored = _dataAccess.GetVehicles();
|
var vehiclesStored = _dataAccess.GetVehicles();
|
||||||
@@ -59,7 +115,8 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||||
}
|
}
|
||||||
var vehicleViewModels = vehiclesStored.Select(x => {
|
var vehicleViewModels = vehiclesStored.Select(x =>
|
||||||
|
{
|
||||||
var vehicleVM = new VehicleViewModel
|
var vehicleVM = new VehicleViewModel
|
||||||
{
|
{
|
||||||
Id = x.Id,
|
Id = x.Id,
|
||||||
@@ -75,7 +132,8 @@ namespace CarCareTracker.Controllers
|
|||||||
OdometerOptional = x.OdometerOptional,
|
OdometerOptional = x.OdometerOptional,
|
||||||
ExtraFields = x.ExtraFields,
|
ExtraFields = x.ExtraFields,
|
||||||
Tags = x.Tags,
|
Tags = x.Tags,
|
||||||
DashboardMetrics = x.DashboardMetrics
|
DashboardMetrics = x.DashboardMetrics,
|
||||||
|
VehicleIdentifier = x.VehicleIdentifier
|
||||||
};
|
};
|
||||||
//dashboard metrics
|
//dashboard metrics
|
||||||
if (x.DashboardMetrics.Any())
|
if (x.DashboardMetrics.Any())
|
||||||
@@ -113,19 +171,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||||
}
|
}
|
||||||
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
|
var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
|
||||||
foreach (Vehicle vehicle in vehiclesStored)
|
|
||||||
{
|
|
||||||
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
|
||||||
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
|
|
||||||
//we don't care about mileages so we can basically fake the current vehicle mileage.
|
|
||||||
if (vehicleReminders.Any())
|
|
||||||
{
|
|
||||||
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, 0, DateTime.Now);
|
|
||||||
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Date = x.Date, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate} - {x.Description}" }).ToList();
|
|
||||||
reminders.AddRange(reminderUrgency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return PartialView("_Calendar", reminders);
|
return PartialView("_Calendar", reminders);
|
||||||
}
|
}
|
||||||
public IActionResult ViewCalendarReminder(int reminderId)
|
public IActionResult ViewCalendarReminder(int reminderId)
|
||||||
@@ -143,17 +189,21 @@ namespace CarCareTracker.Controllers
|
|||||||
UserConfig = userConfig,
|
UserConfig = userConfig,
|
||||||
UILanguages = languages
|
UILanguages = languages
|
||||||
};
|
};
|
||||||
|
return PartialView("_Settings", viewModel);
|
||||||
|
}
|
||||||
|
public async Task<IActionResult> Sponsors()
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var httpClient = new HttpClient();
|
var httpClient = new HttpClient();
|
||||||
var sponsorsData = await httpClient.GetFromJsonAsync<Sponsors>(StaticHelper.SponsorsPath) ?? new Sponsors();
|
var sponsorsData = await httpClient.GetFromJsonAsync<Sponsors>(StaticHelper.SponsorsPath) ?? new Sponsors();
|
||||||
viewModel.Sponsors = sponsorsData;
|
return PartialView("_Sponsors", sponsorsData);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError($"Unable to retrieve sponsors: {ex.Message}");
|
_logger.LogError($"Unable to retrieve sponsors: {ex.Message}");
|
||||||
|
return PartialView("_Sponsors", new Sponsors());
|
||||||
}
|
}
|
||||||
return PartialView("_Settings", viewModel);
|
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult WriteToSettings(UserConfig userConfig)
|
public IActionResult WriteToSettings(UserConfig userConfig)
|
||||||
@@ -175,10 +225,6 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _config.SaveUserConfig(User, existingConfig);
|
var result = _config.SaveUserConfig(User, existingConfig);
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
public IActionResult Privacy()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
public IActionResult GetExtraFieldsModal(int importMode = 0)
|
public IActionResult GetExtraFieldsModal(int importMode = 0)
|
||||||
{
|
{
|
||||||
@@ -216,7 +262,8 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
return Json(false);
|
return Json(false);
|
||||||
} catch (Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex.Message);
|
_logger.LogError(ex.Message);
|
||||||
return Json(false);
|
return Json(false);
|
||||||
@@ -233,7 +280,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var result = _loginLogic.UpdateUserDetails(userId, userAccount);
|
var result = _loginLogic.UpdateUserDetails(userId, userAccount);
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage});
|
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -255,6 +302,260 @@ namespace CarCareTracker.Controllers
|
|||||||
var userName = User.Identity.Name;
|
var userName = User.Identity.Name;
|
||||||
return PartialView("_RootAccountModal", new UserData() { UserName = userName });
|
return PartialView("_RootAccountModal", new UserData() { UserName = userName });
|
||||||
}
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetTranslatorEditor(string userLanguage)
|
||||||
|
{
|
||||||
|
var translationData = _translationHelper.GetTranslations(userLanguage);
|
||||||
|
return PartialView("_TranslationEditor", translationData);
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveTranslation(string userLanguage, Dictionary<string, string> translationData)
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(userLanguage, translationData);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult ExportTranslation(Dictionary<string, string> translationData)
|
||||||
|
{
|
||||||
|
var result = _translationHelper.ExportTranslation(translationData);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetAvailableTranslations()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
var translations = await httpClient.GetFromJsonAsync<Translations>(StaticHelper.TranslationDirectoryPath) ?? new Translations();
|
||||||
|
return PartialView("_Translations", translations);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
|
||||||
|
return PartialView("_Translations", new Translations());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> DownloadTranslation(string continent, string name)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath(continent, name)) ?? new Dictionary<string, string>();
|
||||||
|
if (translationData.Any())
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(name, translationData);
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to download translation: {name}");
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
return Json(true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to download translation: {ex.Message}");
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> DownloadAllTranslations()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
var translations = await httpClient.GetFromJsonAsync<Translations>(StaticHelper.TranslationDirectoryPath) ?? new Translations();
|
||||||
|
int translationsDownloaded = 0;
|
||||||
|
foreach (string translation in translations.Asia)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Asia", translation)) ?? new Dictionary<string, string>();
|
||||||
|
if (translationData.Any())
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(translation, translationData);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
translationsDownloaded++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (string translation in translations.Africa)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Africa", translation)) ?? new Dictionary<string, string>();
|
||||||
|
if (translationData.Any())
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(translation, translationData);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
translationsDownloaded++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (string translation in translations.Europe)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Europe", translation)) ?? new Dictionary<string, string>();
|
||||||
|
if (translationData.Any())
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(translation, translationData);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
translationsDownloaded++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (string translation in translations.NorthAmerica)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("NorthAmerica", translation)) ?? new Dictionary<string, string>();
|
||||||
|
if (translationData.Any())
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(translation, translationData);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
translationsDownloaded++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (string translation in translations.SouthAmerica)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("SouthAmerica", translation)) ?? new Dictionary<string, string>();
|
||||||
|
if (translationData.Any())
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(translation, translationData);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
translationsDownloaded++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (string translation in translations.Oceania)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Oceania", translation)) ?? new Dictionary<string, string>();
|
||||||
|
if (translationData.Any())
|
||||||
|
{
|
||||||
|
var result = _translationHelper.SaveTranslation(translation, translationData);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
translationsDownloaded++;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (translationsDownloaded > 0)
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse() { Success = true, Message = $"{translationsDownloaded} Translations Downloaded" });
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse() { Success = false, Message = "No Translations Downloaded" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
|
||||||
|
return Json(new OperationResponse() { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ActionResult GetVehicleSelector(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehiclesStored = _dataAccess.GetVehicles();
|
||||||
|
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||||
|
}
|
||||||
|
if (vehicleId != default)
|
||||||
|
{
|
||||||
|
vehiclesStored.RemoveAll(x => x.Id == vehicleId);
|
||||||
|
}
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
if (userConfig.HideSoldVehicles)
|
||||||
|
{
|
||||||
|
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
|
||||||
|
}
|
||||||
|
return PartialView("_VehicleSelector", vehiclesStored);
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetCustomWidgetEditor()
|
||||||
|
{
|
||||||
|
if (_config.GetCustomWidgetsEnabled())
|
||||||
|
{
|
||||||
|
var customWidgetData = _fileHelper.GetWidgets();
|
||||||
|
return PartialView("_WidgetEditor", customWidgetData);
|
||||||
|
}
|
||||||
|
return Json(string.Empty);
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveCustomWidgets(string widgetsData)
|
||||||
|
{
|
||||||
|
if (_config.GetCustomWidgetsEnabled())
|
||||||
|
{
|
||||||
|
var saveResult = _fileHelper.SaveWidgets(widgetsData);
|
||||||
|
return Json(saveResult);
|
||||||
|
}
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteCustomWidgets()
|
||||||
|
{
|
||||||
|
if (_config.GetCustomWidgetsEnabled())
|
||||||
|
{
|
||||||
|
var deleteResult = _fileHelper.DeleteWidgets();
|
||||||
|
return Json(deleteResult);
|
||||||
|
}
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
public IActionResult Error()
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using JsonSerializer=System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
namespace CarCareTracker.Controllers
|
namespace CarCareTracker.Controllers
|
||||||
{
|
{
|
||||||
@@ -34,6 +35,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
var cmds = new List<string>
|
var cmds = new List<string>
|
||||||
{
|
{
|
||||||
|
"CREATE SCHEMA IF NOT EXISTS app",
|
||||||
"CREATE TABLE IF NOT EXISTS app.vehicles (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)",
|
"CREATE TABLE IF NOT EXISTS app.vehicles (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)",
|
||||||
"CREATE TABLE IF NOT EXISTS app.collisionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
"CREATE TABLE IF NOT EXISTS app.collisionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||||
"CREATE TABLE IF NOT EXISTS app.upgraderecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
"CREATE TABLE IF NOT EXISTS app.upgraderecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
|
||||||
@@ -104,7 +106,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
Vehicle vehicle = System.Text.Json.JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
Vehicle vehicle = JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||||
vehicles.Add(vehicle);
|
vehicles.Add(vehicle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +124,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
repairrecords.Add(System.Text.Json.JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
|
repairrecords.Add(JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in repairrecords)
|
foreach (var record in repairrecords)
|
||||||
@@ -139,7 +141,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
upgraderecords.Add(System.Text.Json.JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
|
upgraderecords.Add(JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in upgraderecords)
|
foreach (var record in upgraderecords)
|
||||||
@@ -156,7 +158,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
servicerecords.Add(System.Text.Json.JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
|
servicerecords.Add(JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in servicerecords)
|
foreach (var record in servicerecords)
|
||||||
@@ -175,7 +177,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
gasrecords.Add(System.Text.Json.JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
|
gasrecords.Add(JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in gasrecords)
|
foreach (var record in gasrecords)
|
||||||
@@ -192,7 +194,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
noterecords.Add(System.Text.Json.JsonSerializer.Deserialize<Note>(reader["data"] as string));
|
noterecords.Add(JsonSerializer.Deserialize<Note>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in noterecords)
|
foreach (var record in noterecords)
|
||||||
@@ -209,7 +211,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
odometerrecords.Add(System.Text.Json.JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
|
odometerrecords.Add(JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in odometerrecords)
|
foreach (var record in odometerrecords)
|
||||||
@@ -226,7 +228,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
reminderrecords.Add(System.Text.Json.JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
|
reminderrecords.Add(JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in reminderrecords)
|
foreach (var record in reminderrecords)
|
||||||
@@ -245,7 +247,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
planrecords.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
|
planrecords.Add(JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in planrecords)
|
foreach (var record in planrecords)
|
||||||
@@ -262,7 +264,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
planrecordtemplates.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
|
planrecordtemplates.Add(JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in planrecordtemplates)
|
foreach (var record in planrecordtemplates)
|
||||||
@@ -279,7 +281,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
supplyrecords.Add(System.Text.Json.JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
|
supplyrecords.Add(JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in supplyrecords)
|
foreach (var record in supplyrecords)
|
||||||
@@ -296,7 +298,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
taxrecords.Add(System.Text.Json.JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
|
taxrecords.Add(JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in taxrecords)
|
foreach (var record in taxrecords)
|
||||||
@@ -359,7 +361,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
userconfigrecords.Add(System.Text.Json.JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
|
userconfigrecords.Add(JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in userconfigrecords)
|
foreach (var record in userconfigrecords)
|
||||||
@@ -403,7 +405,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
extrafields.Add(System.Text.Json.JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
|
extrafields.Add(JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var record in extrafields)
|
foreach (var record in extrafields)
|
||||||
@@ -474,7 +476,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", vehicle.Id);
|
ctext.Parameters.AddWithValue("id", vehicle.Id);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(vehicle));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(vehicle));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -490,7 +492,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,7 +508,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,7 +524,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -540,7 +542,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -556,7 +558,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -572,7 +574,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -588,7 +590,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -606,7 +608,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -622,7 +624,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,7 +640,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -654,7 +656,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,7 +707,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -737,7 +739,7 @@ namespace CarCareTracker.Controllers
|
|||||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||||
{
|
{
|
||||||
ctext.Parameters.AddWithValue("id", record.Id);
|
ctext.Parameters.AddWithValue("id", record.Id);
|
||||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
187
Controllers/Vehicle/GasController.cs
Normal file
187
Controllers/Vehicle/GasController.cs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetGasRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
//check if the user uses MPG or Liters per 100km.
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
bool useMPG = userConfig.UseMPG;
|
||||||
|
bool useUKMPG = userConfig.UseUKMPG;
|
||||||
|
var computedResults = _gasHelper.GetGasRecordViewModels(result, useMPG, useUKMPG);
|
||||||
|
if (userConfig.UseDescending)
|
||||||
|
{
|
||||||
|
computedResults = computedResults.OrderByDescending(x => DateTime.Parse(x.Date)).ThenByDescending(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
var vehicleIsElectric = vehicleData.IsElectric;
|
||||||
|
var vehicleUseHours = vehicleData.UseHours;
|
||||||
|
var viewModel = new GasRecordViewModelContainer()
|
||||||
|
{
|
||||||
|
UseKwh = vehicleIsElectric,
|
||||||
|
UseHours = vehicleUseHours,
|
||||||
|
GasRecords = computedResults
|
||||||
|
};
|
||||||
|
return PartialView("_Gas", viewModel);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
|
||||||
|
{
|
||||||
|
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = DateTime.Parse(gasRecord.Date),
|
||||||
|
VehicleId = gasRecord.VehicleId,
|
||||||
|
Mileage = gasRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Gas Record. {gasRecord.Notes}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
gasRecord.Files = gasRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), gasRecord.VehicleId, User.Identity.Name, $"{(gasRecord.Id == default ? "Created" : "Edited")} Gas Record - Mileage: {gasRecord.Mileage.ToString()}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddGasRecordPartialView(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
var vehicleIsElectric = vehicleData.IsElectric;
|
||||||
|
var vehicleUseHours = vehicleData.UseHours;
|
||||||
|
return PartialView("_GasModal", new GasRecordInputContainer() { UseKwh = vehicleIsElectric, UseHours = vehicleUseHours, GasRecord = new GasRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields } });
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetGasRecordForEditById(int gasRecordId)
|
||||||
|
{
|
||||||
|
var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
|
||||||
|
var convertedResult = new GasRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Mileage = result.Mileage,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Files = result.Files,
|
||||||
|
Gallons = result.Gallons,
|
||||||
|
IsFillToFull = result.IsFillToFull,
|
||||||
|
MissedFuelUp = result.MissedFuelUp,
|
||||||
|
Notes = result.Notes,
|
||||||
|
Tags = result.Tags,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(convertedResult.VehicleId);
|
||||||
|
var vehicleIsElectric = vehicleData.IsElectric;
|
||||||
|
var vehicleUseHours = vehicleData.UseHours;
|
||||||
|
var viewModel = new GasRecordInputContainer()
|
||||||
|
{
|
||||||
|
UseKwh = vehicleIsElectric,
|
||||||
|
UseHours = vehicleUseHours,
|
||||||
|
GasRecord = convertedResult
|
||||||
|
};
|
||||||
|
return PartialView("_GasModal", viewModel);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteGasRecordById(int gasRecordId)
|
||||||
|
{
|
||||||
|
var result = _gasRecordDataAccess.DeleteGasRecordById(gasRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveUserGasTabPreferences(string gasUnit, string fuelMileageUnit)
|
||||||
|
{
|
||||||
|
var currentConfig = _config.GetUserConfig(User);
|
||||||
|
currentConfig.PreferredGasUnit = gasUnit;
|
||||||
|
currentConfig.PreferredGasMileageUnit = fuelMileageUnit;
|
||||||
|
var result = _config.SaveUserConfig(User, currentConfig);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetGasRecordsEditModal(List<int> recordIds)
|
||||||
|
{
|
||||||
|
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields;
|
||||||
|
return PartialView("_GasRecordsModal", new GasRecordEditModel { RecordIds = recordIds, EditRecord = new GasRecord { ExtraFields = extraFields } });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveMultipleGasRecords(GasRecordEditModel editModel)
|
||||||
|
{
|
||||||
|
var dateIsEdited = editModel.EditRecord.Date != default;
|
||||||
|
var mileageIsEdited = editModel.EditRecord.Mileage != default;
|
||||||
|
var consumptionIsEdited = editModel.EditRecord.Gallons != default;
|
||||||
|
var costIsEdited = editModel.EditRecord.Cost != default;
|
||||||
|
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
|
||||||
|
var tagsIsEdited = editModel.EditRecord.Tags.Any();
|
||||||
|
var extraFieldIsEdited = editModel.EditRecord.ExtraFields.Any();
|
||||||
|
//handle clear overrides
|
||||||
|
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Tags = new List<string>();
|
||||||
|
}
|
||||||
|
if (noteIsEdited && editModel.EditRecord.Notes == "---")
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Notes = "";
|
||||||
|
}
|
||||||
|
bool result = false;
|
||||||
|
foreach (int recordId in editModel.RecordIds)
|
||||||
|
{
|
||||||
|
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||||
|
if (dateIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Date = editModel.EditRecord.Date;
|
||||||
|
}
|
||||||
|
if (consumptionIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Gallons = editModel.EditRecord.Gallons;
|
||||||
|
}
|
||||||
|
if (costIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Cost = editModel.EditRecord.Cost;
|
||||||
|
}
|
||||||
|
if (mileageIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Mileage = editModel.EditRecord.Mileage;
|
||||||
|
}
|
||||||
|
if (noteIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Notes = editModel.EditRecord.Notes;
|
||||||
|
}
|
||||||
|
if (tagsIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Tags = editModel.EditRecord.Tags;
|
||||||
|
}
|
||||||
|
if (extraFieldIsEdited)
|
||||||
|
{
|
||||||
|
foreach (ExtraField extraField in editModel.EditRecord.ExtraFields)
|
||||||
|
{
|
||||||
|
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
|
||||||
|
{
|
||||||
|
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
|
||||||
|
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
|
||||||
|
existingRecord.ExtraFields.Insert(insertIndex, extraField);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingRecord.ExtraFields.Add(extraField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = _gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
486
Controllers/Vehicle/ImportController.cs
Normal file
486
Controllers/Vehicle/ImportController.cs
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.MapProfile;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetBulkImportModalPartialView(ImportMode mode)
|
||||||
|
{
|
||||||
|
return PartialView("_BulkDataImporter", mode);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
|
||||||
|
{
|
||||||
|
if (vehicleId == default && mode != ImportMode.SupplyRecord)
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
string uploadDirectory = "temp/";
|
||||||
|
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
||||||
|
if (!Directory.Exists(uploadPath))
|
||||||
|
Directory.CreateDirectory(uploadPath);
|
||||||
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||||
|
if (mode == ImportMode.ServiceRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
|
||||||
|
{
|
||||||
|
Date = x.Date.ToShortDateString(),
|
||||||
|
Description = x.Description,
|
||||||
|
Cost = x.Cost.ToString("C"),
|
||||||
|
Notes = x.Notes,
|
||||||
|
Odometer = x.Mileage.ToString(),
|
||||||
|
Tags = string.Join(" ", x.Tags),
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
//custom writer
|
||||||
|
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
writer.Dispose();
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.RepairRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
|
||||||
|
{
|
||||||
|
Date = x.Date.ToShortDateString(),
|
||||||
|
Description = x.Description,
|
||||||
|
Cost = x.Cost.ToString("C"),
|
||||||
|
Notes = x.Notes,
|
||||||
|
Odometer = x.Mileage.ToString(),
|
||||||
|
Tags = string.Join(" ", x.Tags),
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.UpgradeRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
|
||||||
|
{
|
||||||
|
Date = x.Date.ToShortDateString(),
|
||||||
|
Description = x.Description,
|
||||||
|
Cost = x.Cost.ToString("C"),
|
||||||
|
Notes = x.Notes,
|
||||||
|
Odometer = x.Mileage.ToString(),
|
||||||
|
Tags = string.Join(" ", x.Tags),
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.OdometerRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new OdometerRecordExportModel
|
||||||
|
{
|
||||||
|
Date = x.Date.ToShortDateString(),
|
||||||
|
Notes = x.Notes,
|
||||||
|
InitialOdometer = x.InitialMileage.ToString(),
|
||||||
|
Odometer = x.Mileage.ToString(),
|
||||||
|
Tags = string.Join(" ", x.Tags),
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
StaticHelper.WriteOdometerRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.SupplyRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new SupplyRecordExportModel
|
||||||
|
{
|
||||||
|
Date = x.Date.ToShortDateString(),
|
||||||
|
Description = x.Description,
|
||||||
|
Cost = x.Cost.ToString("C"),
|
||||||
|
PartNumber = x.PartNumber,
|
||||||
|
PartQuantity = x.Quantity.ToString(),
|
||||||
|
PartSupplier = x.PartSupplier,
|
||||||
|
Notes = x.Notes,
|
||||||
|
Tags = string.Join(" ", x.Tags),
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
StaticHelper.WriteSupplyRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.TaxRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new TaxRecordExportModel
|
||||||
|
{
|
||||||
|
Date = x.Date.ToShortDateString(),
|
||||||
|
Description = x.Description,
|
||||||
|
Cost = x.Cost.ToString("C"),
|
||||||
|
Notes = x.Notes,
|
||||||
|
Tags = string.Join(" ", x.Tags),
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
StaticHelper.WriteTaxRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.PlanRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new PlanRecordExportModel
|
||||||
|
{
|
||||||
|
DateCreated = x.DateCreated.ToString("G"),
|
||||||
|
DateModified = x.DateModified.ToString("G"),
|
||||||
|
Description = x.Description,
|
||||||
|
Cost = x.Cost.ToString("C"),
|
||||||
|
Type = x.ImportMode.ToString(),
|
||||||
|
Priority = x.Priority.ToString(),
|
||||||
|
Progress = x.Progress.ToString(),
|
||||||
|
Notes = x.Notes,
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
StaticHelper.WritePlanRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.GasRecord)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||||
|
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
||||||
|
var convertedRecords = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG);
|
||||||
|
var exportData = convertedRecords.Select(x => new GasRecordExportModel
|
||||||
|
{
|
||||||
|
Date = x.Date.ToString(),
|
||||||
|
Cost = x.Cost.ToString(),
|
||||||
|
FuelConsumed = x.Gallons.ToString(),
|
||||||
|
FuelEconomy = x.MilesPerGallon.ToString(),
|
||||||
|
Odometer = x.Mileage.ToString(),
|
||||||
|
IsFillToFull = x.IsFillToFull.ToString(),
|
||||||
|
MissedFuelUp = x.MissedFuelUp.ToString(),
|
||||||
|
Notes = x.Notes,
|
||||||
|
Tags = string.Join(" ", x.Tags),
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
});
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
StaticHelper.WriteGasRecordExportModel(csv, exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName)
|
||||||
|
{
|
||||||
|
if (vehicleId == default && mode != ImportMode.SupplyRecord)
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
var fullFileName = _fileHelper.GetFullFilePath(fileName);
|
||||||
|
if (string.IsNullOrWhiteSpace(fullFileName))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(fullFileName))
|
||||||
|
{
|
||||||
|
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
|
||||||
|
config.MissingFieldFound = null;
|
||||||
|
config.HeaderValidated = null;
|
||||||
|
config.PrepareHeaderForMatch = args => { return args.Header.Trim().ToLower(); };
|
||||||
|
using (var csv = new CsvReader(reader, config))
|
||||||
|
{
|
||||||
|
csv.Context.RegisterClassMap<ImportMapper>();
|
||||||
|
var records = csv.GetRecords<ImportModel>().ToList();
|
||||||
|
if (records.Any())
|
||||||
|
{
|
||||||
|
var requiredExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)mode).ExtraFields.Where(x => x.IsRequired).Select(y => y.Name);
|
||||||
|
foreach (ImportModel importModel in records)
|
||||||
|
{
|
||||||
|
if (mode == ImportMode.GasRecord)
|
||||||
|
{
|
||||||
|
//convert to gas model.
|
||||||
|
var convertedRecord = new GasRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||||
|
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
if (string.IsNullOrWhiteSpace(importModel.Cost) && !string.IsNullOrWhiteSpace(importModel.Price))
|
||||||
|
{
|
||||||
|
//cost was not given but price is.
|
||||||
|
//fuelly sometimes exports CSVs without total cost.
|
||||||
|
var parsedPrice = decimal.Parse(importModel.Price, NumberStyles.Any);
|
||||||
|
convertedRecord.Cost = convertedRecord.Gallons * parsedPrice;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
convertedRecord.Cost = decimal.Parse(importModel.Cost, NumberStyles.Any);
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(importModel.IsFillToFull) && !string.IsNullOrWhiteSpace(importModel.PartialFuelUp))
|
||||||
|
{
|
||||||
|
var parsedBool = importModel.PartialFuelUp.Trim() == "1";
|
||||||
|
convertedRecord.IsFillToFull = !parsedBool;
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(importModel.IsFillToFull))
|
||||||
|
{
|
||||||
|
var possibleFillToFullValues = new List<string> { "1", "true", "full" };
|
||||||
|
var parsedBool = possibleFillToFullValues.Contains(importModel.IsFillToFull.Trim().ToLower());
|
||||||
|
convertedRecord.IsFillToFull = parsedBool;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(importModel.MissedFuelUp))
|
||||||
|
{
|
||||||
|
var possibleMissedFuelUpValues = new List<string> { "1", "true" };
|
||||||
|
var parsedBool = possibleMissedFuelUpValues.Contains(importModel.MissedFuelUp.Trim().ToLower());
|
||||||
|
convertedRecord.MissedFuelUp = parsedBool;
|
||||||
|
}
|
||||||
|
//insert record into db, check to make sure fuelconsumed is not zero so we don't get a divide by zero error.
|
||||||
|
if (convertedRecord.Gallons > 0)
|
||||||
|
{
|
||||||
|
_gasRecordDataAccess.SaveGasRecordToVehicle(convertedRecord);
|
||||||
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = convertedRecord.Date,
|
||||||
|
VehicleId = convertedRecord.VehicleId,
|
||||||
|
Mileage = convertedRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Gas Record via CSV Import. {convertedRecord.Notes}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.ServiceRecord)
|
||||||
|
{
|
||||||
|
var convertedRecord = new ServiceRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||||
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {importModel.Date}" : importModel.Description,
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||||
|
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
_serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
|
||||||
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = convertedRecord.Date,
|
||||||
|
VehicleId = convertedRecord.VehicleId,
|
||||||
|
Mileage = convertedRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Service Record via CSV Import. {convertedRecord.Notes}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.OdometerRecord)
|
||||||
|
{
|
||||||
|
var convertedRecord = new OdometerRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
InitialMileage = string.IsNullOrWhiteSpace(importModel.InitialOdometer) ? 0 : decimal.ToInt32(decimal.Parse(importModel.InitialOdometer, NumberStyles.Any)),
|
||||||
|
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.PlanRecord)
|
||||||
|
{
|
||||||
|
var progressIsEnum = Enum.TryParse(importModel.Progress, out PlanProgress parsedProgress);
|
||||||
|
var typeIsEnum = Enum.TryParse(importModel.Type, out ImportMode parsedType);
|
||||||
|
var priorityIsEnum = Enum.TryParse(importModel.Priority, out PlanPriority parsedPriority);
|
||||||
|
var convertedRecord = new PlanRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
DateCreated = DateTime.Parse(importModel.DateCreated),
|
||||||
|
DateModified = DateTime.Parse(importModel.DateModified),
|
||||||
|
Progress = parsedProgress,
|
||||||
|
ImportMode = parsedType,
|
||||||
|
Priority = parsedPriority,
|
||||||
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Plan Record on {importModel.DateCreated}" : importModel.Description,
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
_planRecordDataAccess.SavePlanRecordToVehicle(convertedRecord);
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.RepairRecord)
|
||||||
|
{
|
||||||
|
var convertedRecord = new CollisionRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||||
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {importModel.Date}" : importModel.Description,
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||||
|
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
|
||||||
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = convertedRecord.Date,
|
||||||
|
VehicleId = convertedRecord.VehicleId,
|
||||||
|
Mileage = convertedRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Repair Record via CSV Import. {convertedRecord.Notes}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.UpgradeRecord)
|
||||||
|
{
|
||||||
|
var convertedRecord = new UpgradeRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
|
||||||
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {importModel.Date}" : importModel.Description,
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||||
|
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
|
||||||
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = convertedRecord.Date,
|
||||||
|
VehicleId = convertedRecord.VehicleId,
|
||||||
|
Mileage = convertedRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Upgrade Record via CSV Import. {convertedRecord.Notes}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.SupplyRecord)
|
||||||
|
{
|
||||||
|
var convertedRecord = new SupplyRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
PartNumber = importModel.PartNumber,
|
||||||
|
PartSupplier = importModel.PartSupplier,
|
||||||
|
Quantity = decimal.Parse(importModel.PartQuantity, NumberStyles.Any),
|
||||||
|
Description = importModel.Description,
|
||||||
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||||
|
Notes = importModel.Notes,
|
||||||
|
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(convertedRecord);
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.TaxRecord)
|
||||||
|
{
|
||||||
|
var convertedRecord = new TaxRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {importModel.Date}" : importModel.Description,
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
|
||||||
|
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
|
||||||
|
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
|
||||||
|
};
|
||||||
|
_taxRecordDataAccess.SaveTaxRecordToVehicle(convertedRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json(true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error Occurred While Bulk Inserting");
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
Controllers/Vehicle/NoteController.cs
Normal file
78
Controllers/Vehicle/NoteController.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetNotesByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
|
||||||
|
result = result.OrderByDescending(x => x.Pinned).ToList();
|
||||||
|
return PartialView("_Notes", result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetPinnedNotesByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
|
||||||
|
result = result.Where(x => x.Pinned).ToList();
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveNoteToVehicleId(Note note)
|
||||||
|
{
|
||||||
|
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), note.VehicleId, User.Identity.Name, $"{(note.Id == default ? "Created" : "Edited")} Note - Description: {note.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddNotePartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_NoteModal", new Note());
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetNoteForEditById(int noteId)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.GetNoteById(noteId);
|
||||||
|
return PartialView("_NoteModal", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteNoteById(int noteId)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.DeleteNoteById(noteId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult PinNotes(List<int> noteIds, bool isToggle = false, bool pinStatus = false)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
foreach (int noteId in noteIds)
|
||||||
|
{
|
||||||
|
var existingNote = _noteDataAccess.GetNoteById(noteId);
|
||||||
|
if (isToggle)
|
||||||
|
{
|
||||||
|
existingNote.Pinned = !existingNote.Pinned;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingNote.Pinned = pinStatus;
|
||||||
|
}
|
||||||
|
result = _noteDataAccess.SaveNoteToVehicle(existingNote);
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
154
Controllers/Vehicle/OdometerController.cs
Normal file
154
Controllers/Vehicle/OdometerController.cs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult ForceRecalculateDistanceByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
result = _odometerLogic.AutoConvertOdometerRecord(result);
|
||||||
|
return Json(result.Any());
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetOdometerRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
//determine if conversion is needed.
|
||||||
|
if (result.All(x => x.InitialMileage == default))
|
||||||
|
{
|
||||||
|
result = _odometerLogic.AutoConvertOdometerRecord(result);
|
||||||
|
}
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
return PartialView("_OdometerRecords", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord)
|
||||||
|
{
|
||||||
|
//move files from temp.
|
||||||
|
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), odometerRecord.VehicleId, User.Identity.Name, $"{(odometerRecord.Id == default ? "Created" : "Edited")} Odometer Record - Mileage: {odometerRecord.Mileage.ToString()}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
|
||||||
|
{
|
||||||
|
return PartialView("_OdometerRecordModal", new OdometerRecordInput() { InitialMileage = _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()), ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetOdometerRecordsEditModal(List<int> recordIds)
|
||||||
|
{
|
||||||
|
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields;
|
||||||
|
return PartialView("_OdometerRecordsModal", new OdometerRecordEditModel { RecordIds = recordIds, EditRecord = new OdometerRecord { ExtraFields = extraFields } });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveMultipleOdometerRecords(OdometerRecordEditModel editModel)
|
||||||
|
{
|
||||||
|
var dateIsEdited = editModel.EditRecord.Date != default;
|
||||||
|
var initialMileageIsEdited = editModel.EditRecord.InitialMileage != default;
|
||||||
|
var mileageIsEdited = editModel.EditRecord.Mileage != default;
|
||||||
|
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
|
||||||
|
var tagsIsEdited = editModel.EditRecord.Tags.Any();
|
||||||
|
var extraFieldIsEdited = editModel.EditRecord.ExtraFields.Any();
|
||||||
|
//handle clear overrides
|
||||||
|
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Tags = new List<string>();
|
||||||
|
}
|
||||||
|
if (noteIsEdited && editModel.EditRecord.Notes == "---")
|
||||||
|
{
|
||||||
|
editModel.EditRecord.Notes = "";
|
||||||
|
}
|
||||||
|
bool result = false;
|
||||||
|
foreach (int recordId in editModel.RecordIds)
|
||||||
|
{
|
||||||
|
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
|
||||||
|
if (dateIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Date = editModel.EditRecord.Date;
|
||||||
|
}
|
||||||
|
if (initialMileageIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.InitialMileage = editModel.EditRecord.InitialMileage;
|
||||||
|
}
|
||||||
|
if (mileageIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Mileage = editModel.EditRecord.Mileage;
|
||||||
|
}
|
||||||
|
if (noteIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Notes = editModel.EditRecord.Notes;
|
||||||
|
}
|
||||||
|
if (tagsIsEdited)
|
||||||
|
{
|
||||||
|
existingRecord.Tags = editModel.EditRecord.Tags;
|
||||||
|
}
|
||||||
|
if (extraFieldIsEdited)
|
||||||
|
{
|
||||||
|
foreach (ExtraField extraField in editModel.EditRecord.ExtraFields)
|
||||||
|
{
|
||||||
|
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
|
||||||
|
{
|
||||||
|
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
|
||||||
|
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
|
||||||
|
existingRecord.ExtraFields.Insert(insertIndex, extraField);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingRecord.ExtraFields.Add(extraField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
|
||||||
|
{
|
||||||
|
var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new OdometerRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
InitialMileage = result.InitialMileage,
|
||||||
|
Mileage = result.Mileage,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Files = result.Files,
|
||||||
|
Tags = result.Tags,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
return PartialView("_OdometerRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
|
||||||
|
{
|
||||||
|
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(odometerRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
265
Controllers/Vehicle/PlanController.cs
Normal file
265
Controllers/Vehicle/PlanController.cs
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetPlanRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
|
||||||
|
return PartialView("_PlanRecords", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
|
||||||
|
{
|
||||||
|
//populate createdDate
|
||||||
|
if (planRecord.Id == default)
|
||||||
|
{
|
||||||
|
planRecord.DateCreated = DateTime.Now.ToString("G");
|
||||||
|
}
|
||||||
|
planRecord.DateModified = DateTime.Now.ToString("G");
|
||||||
|
//move files from temp.
|
||||||
|
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
if (planRecord.Supplies.Any())
|
||||||
|
{
|
||||||
|
planRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description);
|
||||||
|
if (planRecord.CopySuppliesAttachment)
|
||||||
|
{
|
||||||
|
planRecord.Files.AddRange(GetSuppliesAttachments(planRecord.Supplies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), planRecord.VehicleId, User.Identity.Name, $"{(planRecord.Id == default ? "Created" : "Edited")} Plan Record - Description: {planRecord.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord)
|
||||||
|
{
|
||||||
|
//check if template name already taken.
|
||||||
|
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
|
||||||
|
if (planRecord.Id == default && existingRecord)
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "A template with that description already exists for this vehicle" });
|
||||||
|
}
|
||||||
|
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
var result = _planRecordTemplateDataAccess.SavePlanRecordTemplateToVehicle(planRecord);
|
||||||
|
return Json(new OperationResponse { Success = result, Message = result ? "Template Added" : StaticHelper.GenericErrorMessage });
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetPlanRecordTemplatesForVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(vehicleId);
|
||||||
|
return PartialView("_PlanRecordTemplateModal", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId)
|
||||||
|
{
|
||||||
|
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult OrderPlanSupplies(int planRecordTemplateId)
|
||||||
|
{
|
||||||
|
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||||
|
if (existingRecord.Id == default)
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "Unable to find template" });
|
||||||
|
}
|
||||||
|
if (existingRecord.Supplies.Any())
|
||||||
|
{
|
||||||
|
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
||||||
|
return PartialView("_PlanOrderSupplies", suppliesToOrder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "Template has No Supplies" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult ConvertPlanRecordTemplateToPlanRecord(int planRecordTemplateId)
|
||||||
|
{
|
||||||
|
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||||
|
if (existingRecord.Id == default)
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "Unable to find template" });
|
||||||
|
}
|
||||||
|
if (existingRecord.Supplies.Any())
|
||||||
|
{
|
||||||
|
//check if all supplies are available
|
||||||
|
var supplyAvailability = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
||||||
|
if (supplyAvailability.Any(x => x.Missing))
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "Missing Supplies, Please Delete This Template and Recreate It." });
|
||||||
|
}
|
||||||
|
else if (supplyAvailability.Any(x => x.Insufficient))
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "Insufficient Supplies" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (existingRecord.ReminderRecordId != default)
|
||||||
|
{
|
||||||
|
//check if reminder still exists and is still recurring.
|
||||||
|
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(existingRecord.ReminderRecordId);
|
||||||
|
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "Missing or Non-recurring Reminder, Please Delete This Template and Recreate It." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//populate createdDate
|
||||||
|
existingRecord.DateCreated = DateTime.Now.ToString("G");
|
||||||
|
existingRecord.DateModified = DateTime.Now.ToString("G");
|
||||||
|
existingRecord.Id = default;
|
||||||
|
if (existingRecord.Supplies.Any())
|
||||||
|
{
|
||||||
|
existingRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(existingRecord.Supplies, DateTime.Parse(existingRecord.DateCreated), existingRecord.Description);
|
||||||
|
if (existingRecord.CopySuppliesAttachment)
|
||||||
|
{
|
||||||
|
existingRecord.Files.AddRange(GetSuppliesAttachments(existingRecord.Supplies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord.ToPlanRecord());
|
||||||
|
return Json(new OperationResponse { Success = result, Message = result ? "Plan Record Added" : StaticHelper.GenericErrorMessage });
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddPlanRecordPartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetAddPlanRecordPartialView(PlanRecordInput? planModel)
|
||||||
|
{
|
||||||
|
if (planModel is not null)
|
||||||
|
{
|
||||||
|
planModel.ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields;
|
||||||
|
return PartialView("_PlanRecordModal", planModel);
|
||||||
|
}
|
||||||
|
return PartialView("_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0)
|
||||||
|
{
|
||||||
|
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
||||||
|
existingRecord.Progress = planProgress;
|
||||||
|
existingRecord.DateModified = DateTime.Now;
|
||||||
|
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
|
||||||
|
if (planProgress == PlanProgress.Done)
|
||||||
|
{
|
||||||
|
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = DateTime.Now.Date,
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Mileage = odometer,
|
||||||
|
Notes = $"Auto Insert From Plan Record: {existingRecord.Description}",
|
||||||
|
ExtraFields = existingRecord.ExtraFields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//convert plan record to service/upgrade/repair record.
|
||||||
|
if (existingRecord.ImportMode == ImportMode.ServiceRecord)
|
||||||
|
{
|
||||||
|
var newRecord = new ServiceRecord()
|
||||||
|
{
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Date = DateTime.Now.Date,
|
||||||
|
Mileage = odometer,
|
||||||
|
Description = existingRecord.Description,
|
||||||
|
Cost = existingRecord.Cost,
|
||||||
|
Notes = existingRecord.Notes,
|
||||||
|
Files = existingRecord.Files,
|
||||||
|
RequisitionHistory = existingRecord.RequisitionHistory,
|
||||||
|
ExtraFields = existingRecord.ExtraFields
|
||||||
|
};
|
||||||
|
_serviceRecordDataAccess.SaveServiceRecordToVehicle(newRecord);
|
||||||
|
}
|
||||||
|
else if (existingRecord.ImportMode == ImportMode.RepairRecord)
|
||||||
|
{
|
||||||
|
var newRecord = new CollisionRecord()
|
||||||
|
{
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Date = DateTime.Now.Date,
|
||||||
|
Mileage = odometer,
|
||||||
|
Description = existingRecord.Description,
|
||||||
|
Cost = existingRecord.Cost,
|
||||||
|
Notes = existingRecord.Notes,
|
||||||
|
Files = existingRecord.Files,
|
||||||
|
RequisitionHistory = existingRecord.RequisitionHistory,
|
||||||
|
ExtraFields = existingRecord.ExtraFields
|
||||||
|
};
|
||||||
|
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(newRecord);
|
||||||
|
}
|
||||||
|
else if (existingRecord.ImportMode == ImportMode.UpgradeRecord)
|
||||||
|
{
|
||||||
|
var newRecord = new UpgradeRecord()
|
||||||
|
{
|
||||||
|
VehicleId = existingRecord.VehicleId,
|
||||||
|
Date = DateTime.Now.Date,
|
||||||
|
Mileage = odometer,
|
||||||
|
Description = existingRecord.Description,
|
||||||
|
Cost = existingRecord.Cost,
|
||||||
|
Notes = existingRecord.Notes,
|
||||||
|
Files = existingRecord.Files,
|
||||||
|
RequisitionHistory = existingRecord.RequisitionHistory,
|
||||||
|
ExtraFields = existingRecord.ExtraFields
|
||||||
|
};
|
||||||
|
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(newRecord);
|
||||||
|
}
|
||||||
|
//push back any reminders
|
||||||
|
if (existingRecord.ReminderRecordId != default)
|
||||||
|
{
|
||||||
|
PushbackRecurringReminderRecordWithChecks(existingRecord.ReminderRecordId, DateTime.Now, odometer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetPlanRecordTemplateForEditById(int planRecordTemplateId)
|
||||||
|
{
|
||||||
|
var result = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||||
|
return PartialView("_PlanRecordTemplateEditModal", result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetPlanRecordForEditById(int planRecordId)
|
||||||
|
{
|
||||||
|
var result = _planRecordDataAccess.GetPlanRecordById(planRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new PlanRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Description = result.Description,
|
||||||
|
DateCreated = result.DateCreated.ToString("G"),
|
||||||
|
DateModified = result.DateModified.ToString("G"),
|
||||||
|
ImportMode = result.ImportMode,
|
||||||
|
Priority = result.Priority,
|
||||||
|
Progress = result.Progress,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Files = result.Files,
|
||||||
|
RequisitionHistory = result.RequisitionHistory,
|
||||||
|
ReminderRecordId = result.ReminderRecordId,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
return PartialView("_PlanRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeletePlanRecordById(int planRecordId)
|
||||||
|
{
|
||||||
|
var result = _planRecordDataAccess.DeletePlanRecordById(planRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
163
Controllers/Vehicle/ReminderController.cs
Normal file
163
Controllers/Vehicle/ReminderController.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
private List<ReminderRecordViewModel> GetRemindersAndUrgency(int vehicleId, DateTime dateCompare)
|
||||||
|
{
|
||||||
|
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
|
||||||
|
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||||
|
List<ReminderRecordViewModel> results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, dateCompare);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
private bool GetAndUpdateVehicleUrgentOrPastDueReminders(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
|
||||||
|
//check if user wants auto-refresh past-due reminders
|
||||||
|
if (_config.GetUserConfig(User).EnableAutoReminderRefresh)
|
||||||
|
{
|
||||||
|
//check for past due reminders that are eligible for recurring.
|
||||||
|
var pastDueAndRecurring = result.Where(x => x.Urgency == ReminderUrgency.PastDue && x.IsRecurring);
|
||||||
|
if (pastDueAndRecurring.Any())
|
||||||
|
{
|
||||||
|
foreach (ReminderRecordViewModel reminderRecord in pastDueAndRecurring)
|
||||||
|
{
|
||||||
|
//update based on recurring intervals.
|
||||||
|
//pull reminderRecord based on ID
|
||||||
|
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecord.Id);
|
||||||
|
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder, null, null);
|
||||||
|
//save to db.
|
||||||
|
_reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);
|
||||||
|
//set urgency to not urgent so it gets excluded in count.
|
||||||
|
reminderRecord.Urgency = ReminderUrgency.NotUrgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//check for very urgent or past due reminders that were not eligible for recurring.
|
||||||
|
var pastDueAndUrgentReminders = result.Where(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
|
||||||
|
if (pastDueAndUrgentReminders.Any())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetVehicleHaveUrgentOrPastDueReminders(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = GetAndUpdateVehicleUrgentOrPastDueReminders(vehicleId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetReminderRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
|
||||||
|
result = result.OrderByDescending(x => x.Urgency).ToList();
|
||||||
|
return PartialView("_ReminderRecords", result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||||
|
result.RemoveAll(x => !x.IsRecurring);
|
||||||
|
return PartialView("_RecurringReminderSelector", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult PushbackRecurringReminderRecord(int reminderRecordId)
|
||||||
|
{
|
||||||
|
var result = PushbackRecurringReminderRecordWithChecks(reminderRecordId, null, null);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
private bool PushbackRecurringReminderRecordWithChecks(int reminderRecordId, DateTime? currentDate, decimal? currentMileage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
|
||||||
|
if (existingReminder is not null && existingReminder.Id != default && existingReminder.IsRecurring)
|
||||||
|
{
|
||||||
|
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder, currentDate, currentMileage);
|
||||||
|
//save to db.
|
||||||
|
var reminderUpdateResult = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);
|
||||||
|
if (!reminderUpdateResult)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to update reminder either because the reminder no longer exists or is no longer recurring");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to update reminder because it no longer exists.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
|
||||||
|
{
|
||||||
|
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), reminderRecord.VehicleId, User.Identity.Name, $"{(reminderRecord.Id == default ? "Created" : "Edited")} Reminder - Description: {reminderRecord.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetAddReminderRecordPartialView(ReminderRecordInput? reminderModel)
|
||||||
|
{
|
||||||
|
if (reminderModel is not null)
|
||||||
|
{
|
||||||
|
return PartialView("_ReminderRecordModal", reminderModel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return PartialView("_ReminderRecordModal", new ReminderRecordInput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetReminderRecordForEditById(int reminderRecordId)
|
||||||
|
{
|
||||||
|
var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new ReminderRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Description = result.Description,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Mileage = result.Mileage,
|
||||||
|
Metric = result.Metric,
|
||||||
|
IsRecurring = result.IsRecurring,
|
||||||
|
UseCustomThresholds = result.UseCustomThresholds,
|
||||||
|
CustomThresholds = result.CustomThresholds,
|
||||||
|
ReminderMileageInterval = result.ReminderMileageInterval,
|
||||||
|
ReminderMonthInterval = result.ReminderMonthInterval,
|
||||||
|
CustomMileageInterval = result.CustomMileageInterval,
|
||||||
|
CustomMonthInterval = result.CustomMonthInterval,
|
||||||
|
Tags = result.Tags
|
||||||
|
};
|
||||||
|
return PartialView("_ReminderRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteReminderRecordById(int reminderRecordId)
|
||||||
|
{
|
||||||
|
var result = _reminderRecordDataAccess.DeleteReminderRecordById(reminderRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
Controllers/Vehicle/RepairController.cs
Normal file
101
Controllers/Vehicle/RepairController.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetCollisionRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
return PartialView("_CollisionRecords", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord)
|
||||||
|
{
|
||||||
|
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = DateTime.Parse(collisionRecord.Date),
|
||||||
|
VehicleId = collisionRecord.VehicleId,
|
||||||
|
Mileage = collisionRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Repair Record: {collisionRecord.Description}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//move files from temp.
|
||||||
|
collisionRecord.Files = collisionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
if (collisionRecord.Supplies.Any())
|
||||||
|
{
|
||||||
|
collisionRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description);
|
||||||
|
if (collisionRecord.CopySuppliesAttachment)
|
||||||
|
{
|
||||||
|
collisionRecord.Files.AddRange(GetSuppliesAttachments(collisionRecord.Supplies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//push back any reminders
|
||||||
|
if (collisionRecord.ReminderRecordId.Any())
|
||||||
|
{
|
||||||
|
foreach (int reminderRecordId in collisionRecord.ReminderRecordId)
|
||||||
|
{
|
||||||
|
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(collisionRecord.Date), collisionRecord.Mileage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), collisionRecord.VehicleId, User.Identity.Name, $"{(collisionRecord.Id == default ? "Created" : "Edited")} Repair Record - Description: {collisionRecord.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddCollisionRecordPartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_CollisionRecordModal", new CollisionRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
|
||||||
|
{
|
||||||
|
var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new CollisionRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Description = result.Description,
|
||||||
|
Mileage = result.Mileage,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Files = result.Files,
|
||||||
|
Tags = result.Tags,
|
||||||
|
RequisitionHistory = result.RequisitionHistory,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
return PartialView("_CollisionRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
|
||||||
|
{
|
||||||
|
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(collisionRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
568
Controllers/Vehicle/ReportController.cs
Normal file
568
Controllers/Vehicle/ReportController.cs
Normal file
@@ -0,0 +1,568 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetReportPartialView(int vehicleId)
|
||||||
|
{
|
||||||
|
//get records
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
var viewModel = new ReportViewModel();
|
||||||
|
//check if custom widgets are configured
|
||||||
|
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
|
||||||
|
//get totalCostMakeUp
|
||||||
|
viewModel.CostMakeUpForVehicle = new CostMakeUpForVehicle
|
||||||
|
{
|
||||||
|
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
||||||
|
GasRecordSum = gasRecords.Sum(x => x.Cost),
|
||||||
|
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
|
||||||
|
TaxRecordSum = taxRecords.Sum(x => x.Cost),
|
||||||
|
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
|
||||||
|
};
|
||||||
|
//get costbymonth
|
||||||
|
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
|
||||||
|
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, 0));
|
||||||
|
allCosts.AddRange(_reportHelper.GetRepairRecordSum(collisionRecords, 0));
|
||||||
|
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, 0));
|
||||||
|
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, 0));
|
||||||
|
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, 0));
|
||||||
|
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, 0));
|
||||||
|
viewModel.CostForVehicleByMonth = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthName = x.Key.MonthName,
|
||||||
|
Cost = x.Sum(y => y.Cost),
|
||||||
|
DistanceTraveled = x.Max(y => y.DistanceTraveled)
|
||||||
|
}).ToList();
|
||||||
|
//get reminders
|
||||||
|
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now);
|
||||||
|
viewModel.ReminderMakeUpForVehicle = new ReminderMakeUpForVehicle
|
||||||
|
{
|
||||||
|
NotUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count(),
|
||||||
|
UrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.Urgent).Count(),
|
||||||
|
VeryUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count(),
|
||||||
|
PastDueCount = reminders.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()
|
||||||
|
};
|
||||||
|
//populate year dropdown.
|
||||||
|
var numbersArray = new List<int>();
|
||||||
|
if (serviceRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(serviceRecords.Min(x => x.Date.Year));
|
||||||
|
}
|
||||||
|
if (collisionRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(collisionRecords.Min(x => x.Date.Year));
|
||||||
|
}
|
||||||
|
if (gasRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(gasRecords.Min(x => x.Date.Year));
|
||||||
|
}
|
||||||
|
if (upgradeRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(upgradeRecords.Min(x => x.Date.Year));
|
||||||
|
}
|
||||||
|
if (odometerRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(odometerRecords.Min(x => x.Date.Year));
|
||||||
|
}
|
||||||
|
var minYear = numbersArray.Any() ? numbersArray.Min() : DateTime.Now.AddYears(-5).Year;
|
||||||
|
var yearDifference = DateTime.Now.Year - minYear + 1;
|
||||||
|
for (int i = 0; i < yearDifference; i++)
|
||||||
|
{
|
||||||
|
viewModel.Years.Add(DateTime.Now.AddYears(i * -1).Year);
|
||||||
|
}
|
||||||
|
//get collaborators
|
||||||
|
var collaborators = _userLogic.GetCollaboratorsForVehicle(vehicleId);
|
||||||
|
viewModel.Collaborators = collaborators;
|
||||||
|
//get MPG per month.
|
||||||
|
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
|
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||||
|
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
|
mileageData.RemoveAll(x => x.MilesPerGallon == default);
|
||||||
|
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
|
||||||
|
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
|
||||||
|
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
Cost = x.Average(y => y.MilesPerGallon)
|
||||||
|
}));
|
||||||
|
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
|
Cost = x.Sum(y => y.Cost)
|
||||||
|
}).ToList();
|
||||||
|
if (invertedFuelMileageUnit)
|
||||||
|
{
|
||||||
|
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
|
||||||
|
{
|
||||||
|
if (monthMileage.Cost != default)
|
||||||
|
{
|
||||||
|
monthMileage.Cost = 100 / monthMileage.Cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mpgViewModel = new MPGForVehicleByMonth {
|
||||||
|
CostData = monthlyMileageData,
|
||||||
|
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
|
||||||
|
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
|
||||||
|
};
|
||||||
|
viewModel.FuelMileageForVehicleByMonth = mpgViewModel;
|
||||||
|
return PartialView("_Report", viewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetCollaboratorsForVehicle(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _userLogic.GetCollaboratorsForVehicle(vehicleId);
|
||||||
|
return PartialView("_Collaborators", result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult AddCollaboratorsToVehicle(int vehicleId, string username)
|
||||||
|
{
|
||||||
|
var result = _userLogic.AddCollaboratorToVehicle(vehicleId, username);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteCollaboratorFromVehicle(int userId, int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _userLogic.DeleteCollaboratorFromVehicle(userId, vehicleId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetCostMakeUpForVehicle(int vehicleId, int year = 0)
|
||||||
|
{
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
serviceRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
gasRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
collisionRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
taxRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
upgradeRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
var viewModel = new CostMakeUpForVehicle
|
||||||
|
{
|
||||||
|
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
||||||
|
GasRecordSum = gasRecords.Sum(x => x.Cost),
|
||||||
|
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
|
||||||
|
TaxRecordSum = taxRecords.Sum(x => x.Cost),
|
||||||
|
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
|
||||||
|
};
|
||||||
|
return PartialView("_CostMakeUpReport", viewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetCostTableForVehicle(int vehicleId, int year = 0)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
|
||||||
|
var serviceRecords = vehicleRecords.ServiceRecords;
|
||||||
|
var gasRecords = vehicleRecords.GasRecords;
|
||||||
|
var collisionRecords = vehicleRecords.CollisionRecords;
|
||||||
|
var taxRecords = vehicleRecords.TaxRecords;
|
||||||
|
var upgradeRecords = vehicleRecords.UpgradeRecords;
|
||||||
|
var odometerRecords = vehicleRecords.OdometerRecords;
|
||||||
|
if (year != default)
|
||||||
|
{
|
||||||
|
serviceRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
gasRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
collisionRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
taxRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
upgradeRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
odometerRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
}
|
||||||
|
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
|
||||||
|
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
var totalDistanceTraveled = maxMileage - minMileage;
|
||||||
|
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
|
||||||
|
var viewModel = new CostTableForVehicle
|
||||||
|
{
|
||||||
|
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
||||||
|
GasRecordSum = gasRecords.Sum(x => x.Cost),
|
||||||
|
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
|
||||||
|
TaxRecordSum = taxRecords.Sum(x => x.Cost),
|
||||||
|
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost),
|
||||||
|
TotalDistance = totalDistanceTraveled,
|
||||||
|
DistanceUnit = vehicleData.UseHours ? "Cost Per Hour" : userConfig.UseMPG ? "Cost Per Mile" : "Cost Per Kilometer",
|
||||||
|
NumberOfDays = totalDays
|
||||||
|
};
|
||||||
|
return PartialView("_CostTableReport", viewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
public IActionResult GetReminderMakeUpByVehicle(int vehicleId, int daysToAdd)
|
||||||
|
{
|
||||||
|
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now.AddDays(daysToAdd));
|
||||||
|
var viewModel = new ReminderMakeUpForVehicle
|
||||||
|
{
|
||||||
|
NotUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count(),
|
||||||
|
UrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.Urgent).Count(),
|
||||||
|
VeryUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count(),
|
||||||
|
PastDueCount = reminders.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()
|
||||||
|
};
|
||||||
|
return PartialView("_ReminderMakeUpReport", viewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetVehicleAttachments(int vehicleId, List<ImportMode> exportTabs)
|
||||||
|
{
|
||||||
|
List<GenericReportModel> attachmentData = new List<GenericReportModel>();
|
||||||
|
if (exportTabs.Contains(ImportMode.ServiceRecord))
|
||||||
|
{
|
||||||
|
var records = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
DataType = ImportMode.ServiceRecord,
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Files = x.Files
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (exportTabs.Contains(ImportMode.RepairRecord))
|
||||||
|
{
|
||||||
|
var records = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
DataType = ImportMode.RepairRecord,
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Files = x.Files
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (exportTabs.Contains(ImportMode.UpgradeRecord))
|
||||||
|
{
|
||||||
|
var records = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
DataType = ImportMode.UpgradeRecord,
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Files = x.Files
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (exportTabs.Contains(ImportMode.GasRecord))
|
||||||
|
{
|
||||||
|
var records = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
DataType = ImportMode.GasRecord,
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Files = x.Files
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (exportTabs.Contains(ImportMode.TaxRecord))
|
||||||
|
{
|
||||||
|
var records = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
DataType = ImportMode.TaxRecord,
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = 0,
|
||||||
|
Files = x.Files
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (exportTabs.Contains(ImportMode.OdometerRecord))
|
||||||
|
{
|
||||||
|
var records = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
DataType = ImportMode.OdometerRecord,
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Files = x.Files
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (exportTabs.Contains(ImportMode.NoteRecord))
|
||||||
|
{
|
||||||
|
var records = _noteDataAccess.GetNotesByVehicleId(vehicleId).Where(x => x.Files.Any());
|
||||||
|
attachmentData.AddRange(records.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
DataType = ImportMode.NoteRecord,
|
||||||
|
Date = DateTime.Now,
|
||||||
|
Odometer = 0,
|
||||||
|
Files = x.Files
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (attachmentData.Any())
|
||||||
|
{
|
||||||
|
attachmentData = attachmentData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
|
||||||
|
var result = _fileHelper.MakeAttachmentsExport(attachmentData);
|
||||||
|
if (string.IsNullOrWhiteSpace(result))
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||||
|
}
|
||||||
|
return Json(new OperationResponse { Success = true, Message = result });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Json(new OperationResponse { Success = false, Message = "No Attachments Found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public IActionResult GetReportParameters()
|
||||||
|
{
|
||||||
|
var viewModel = new ReportParameter() {
|
||||||
|
VisibleColumns = new List<string> {
|
||||||
|
nameof(GenericReportModel.DataType),
|
||||||
|
nameof(GenericReportModel.Date),
|
||||||
|
nameof(GenericReportModel.Odometer),
|
||||||
|
nameof(GenericReportModel.Description),
|
||||||
|
nameof(GenericReportModel.Cost),
|
||||||
|
nameof(GenericReportModel.Notes)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//get all extra fields from service records, repairs, upgrades, and tax records.
|
||||||
|
var recordTypes = new List<int>() { 0, 1, 3, 4 };
|
||||||
|
var extraFields = new List<string>();
|
||||||
|
foreach(int recordType in recordTypes)
|
||||||
|
{
|
||||||
|
extraFields.AddRange(_extraFieldDataAccess.GetExtraFieldsById(recordType).ExtraFields.Select(x => x.Name));
|
||||||
|
}
|
||||||
|
viewModel.ExtraFields = extraFields.Distinct().ToList();
|
||||||
|
|
||||||
|
return PartialView("_ReportParameters", viewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
public IActionResult GetVehicleHistory(int vehicleId, ReportParameter reportParameter)
|
||||||
|
{
|
||||||
|
var vehicleHistory = new VehicleHistoryViewModel();
|
||||||
|
vehicleHistory.ReportParameters = reportParameter;
|
||||||
|
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleId);
|
||||||
|
vehicleHistory.Odometer = maxMileage.ToString("N1");
|
||||||
|
var minMileage = _vehicleLogic.GetMinMileage(vehicleId);
|
||||||
|
var distanceTraveled = maxMileage - minMileage;
|
||||||
|
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
|
||||||
|
{
|
||||||
|
var endDate = vehicleHistory.VehicleData.SoldDate;
|
||||||
|
int daysOwned = 0;
|
||||||
|
if (string.IsNullOrWhiteSpace(endDate))
|
||||||
|
{
|
||||||
|
endDate = DateTime.Now.ToShortDateString();
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
daysOwned = (DateTime.Parse(endDate) - DateTime.Parse(vehicleHistory.VehicleData.PurchaseDate)).Days;
|
||||||
|
vehicleHistory.DaysOwned = daysOwned.ToString("N1");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
vehicleHistory.DaysOwned = string.Empty;
|
||||||
|
}
|
||||||
|
//calculate depreciation
|
||||||
|
var totalDepreciation = vehicleHistory.VehicleData.PurchasePrice - vehicleHistory.VehicleData.SoldPrice;
|
||||||
|
//we only calculate depreciation if a sold price is provided.
|
||||||
|
if (totalDepreciation != default && vehicleHistory.VehicleData.SoldPrice != default)
|
||||||
|
{
|
||||||
|
vehicleHistory.TotalDepreciation = totalDepreciation;
|
||||||
|
if (daysOwned != default)
|
||||||
|
{
|
||||||
|
vehicleHistory.DepreciationPerDay = Math.Abs(totalDepreciation / daysOwned);
|
||||||
|
}
|
||||||
|
if (distanceTraveled != default)
|
||||||
|
{
|
||||||
|
vehicleHistory.DepreciationPerMile = Math.Abs(totalDepreciation / distanceTraveled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<GenericReportModel> reportData = new List<GenericReportModel>();
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||||
|
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
||||||
|
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||||
|
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
|
||||||
|
vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost);
|
||||||
|
vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost);
|
||||||
|
if (distanceTraveled != default)
|
||||||
|
{
|
||||||
|
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N1");
|
||||||
|
vehicleHistory.TotalCostPerMile = vehicleHistory.TotalCost / distanceTraveled;
|
||||||
|
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
|
||||||
|
}
|
||||||
|
var averageMPG = "0";
|
||||||
|
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
|
||||||
|
if (gasViewModels.Any())
|
||||||
|
{
|
||||||
|
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
|
||||||
|
}
|
||||||
|
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleHistory.VehicleData.IsElectric, vehicleHistory.VehicleData.UseHours, useMPG, useUKMPG);
|
||||||
|
if (fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l")
|
||||||
|
{
|
||||||
|
//conversion needed.
|
||||||
|
var newAverageMPG = decimal.Parse(averageMPG, NumberStyles.Any);
|
||||||
|
if (newAverageMPG != 0)
|
||||||
|
{
|
||||||
|
newAverageMPG = 100 / newAverageMPG;
|
||||||
|
}
|
||||||
|
averageMPG = newAverageMPG.ToString("F");
|
||||||
|
fuelEconomyMileageUnit = preferredFuelMileageUnit;
|
||||||
|
}
|
||||||
|
vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}";
|
||||||
|
//insert servicerecords
|
||||||
|
reportData.AddRange(serviceRecords.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Description = x.Description,
|
||||||
|
Notes = x.Notes,
|
||||||
|
Cost = x.Cost,
|
||||||
|
DataType = ImportMode.ServiceRecord,
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
}));
|
||||||
|
//repair records
|
||||||
|
reportData.AddRange(repairRecords.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Description = x.Description,
|
||||||
|
Notes = x.Notes,
|
||||||
|
Cost = x.Cost,
|
||||||
|
DataType = ImportMode.RepairRecord,
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
}));
|
||||||
|
reportData.AddRange(upgradeRecords.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = x.Mileage,
|
||||||
|
Description = x.Description,
|
||||||
|
Notes = x.Notes,
|
||||||
|
Cost = x.Cost,
|
||||||
|
DataType = ImportMode.UpgradeRecord,
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
}));
|
||||||
|
reportData.AddRange(taxRecords.Select(x => new GenericReportModel
|
||||||
|
{
|
||||||
|
Date = x.Date,
|
||||||
|
Odometer = 0,
|
||||||
|
Description = x.Description,
|
||||||
|
Notes = x.Notes,
|
||||||
|
Cost = x.Cost,
|
||||||
|
DataType = ImportMode.TaxRecord,
|
||||||
|
ExtraFields = x.ExtraFields
|
||||||
|
}));
|
||||||
|
vehicleHistory.VehicleHistory = reportData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
|
||||||
|
return PartialView("_VehicleHistory", vehicleHistory);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetMonthMPGByVehicle(int vehicleId, int year = 0)
|
||||||
|
{
|
||||||
|
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
var userConfig = _config.GetUserConfig(User);
|
||||||
|
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
|
||||||
|
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
|
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
|
||||||
|
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
|
if (year != 0)
|
||||||
|
{
|
||||||
|
mileageData.RemoveAll(x => DateTime.Parse(x.Date).Year != year);
|
||||||
|
}
|
||||||
|
mileageData.RemoveAll(x => x.MilesPerGallon == default);
|
||||||
|
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
|
||||||
|
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
Cost = x.Average(y => y.MilesPerGallon)
|
||||||
|
}));
|
||||||
|
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthId = x.Key,
|
||||||
|
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
|
||||||
|
Cost = x.Sum(y => y.Cost)
|
||||||
|
}).ToList();
|
||||||
|
if (invertedFuelMileageUnit)
|
||||||
|
{
|
||||||
|
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
|
||||||
|
{
|
||||||
|
if (monthMileage.Cost != default)
|
||||||
|
{
|
||||||
|
monthMileage.Cost = 100 / monthMileage.Cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mpgViewModel = new MPGForVehicleByMonth
|
||||||
|
{
|
||||||
|
CostData = monthlyMileageData,
|
||||||
|
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
|
||||||
|
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
|
||||||
|
};
|
||||||
|
return PartialView("_MPGByMonthReport", mpgViewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult GetCostByMonthByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
|
||||||
|
{
|
||||||
|
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
|
||||||
|
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
|
||||||
|
{
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, year));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.Contains(ImportMode.RepairRecord))
|
||||||
|
{
|
||||||
|
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
allCosts.AddRange(_reportHelper.GetRepairRecordSum(repairRecords, year));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.Contains(ImportMode.UpgradeRecord))
|
||||||
|
{
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, year));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.Contains(ImportMode.GasRecord))
|
||||||
|
{
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, year));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.Contains(ImportMode.TaxRecord))
|
||||||
|
{
|
||||||
|
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year));
|
||||||
|
}
|
||||||
|
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
|
||||||
|
{
|
||||||
|
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||||
|
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year));
|
||||||
|
}
|
||||||
|
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
|
||||||
|
{
|
||||||
|
MonthName = x.Key.MonthName,
|
||||||
|
Cost = x.Sum(y => y.Cost),
|
||||||
|
DistanceTraveled = x.Max(y => y.DistanceTraveled)
|
||||||
|
}).ToList();
|
||||||
|
return PartialView("_GasCostByMonthReport", groupedRecord);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAdditionalWidgets()
|
||||||
|
{
|
||||||
|
var widgets = _fileHelper.GetWidgets();
|
||||||
|
return PartialView("_ReportWidgets", widgets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
Controllers/Vehicle/ServiceController.cs
Normal file
101
Controllers/Vehicle/ServiceController.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetServiceRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
return PartialView("_ServiceRecords", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
|
||||||
|
{
|
||||||
|
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = DateTime.Parse(serviceRecord.Date),
|
||||||
|
VehicleId = serviceRecord.VehicleId,
|
||||||
|
Mileage = serviceRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Service Record: {serviceRecord.Description}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//move files from temp.
|
||||||
|
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
if (serviceRecord.Supplies.Any())
|
||||||
|
{
|
||||||
|
serviceRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description);
|
||||||
|
if (serviceRecord.CopySuppliesAttachment)
|
||||||
|
{
|
||||||
|
serviceRecord.Files.AddRange(GetSuppliesAttachments(serviceRecord.Supplies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//push back any reminders
|
||||||
|
if (serviceRecord.ReminderRecordId.Any())
|
||||||
|
{
|
||||||
|
foreach (int reminderRecordId in serviceRecord.ReminderRecordId)
|
||||||
|
{
|
||||||
|
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(serviceRecord.Date), serviceRecord.Mileage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), serviceRecord.VehicleId, User.Identity.Name, $"{(serviceRecord.Id == default ? "Created" : "Edited")} Service Record - Description: {serviceRecord.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddServiceRecordPartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_ServiceRecordModal", new ServiceRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetServiceRecordForEditById(int serviceRecordId)
|
||||||
|
{
|
||||||
|
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new ServiceRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Description = result.Description,
|
||||||
|
Mileage = result.Mileage,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Files = result.Files,
|
||||||
|
Tags = result.Tags,
|
||||||
|
RequisitionHistory = result.RequisitionHistory,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
return PartialView("_ServiceRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteServiceRecordById(int serviceRecordId)
|
||||||
|
{
|
||||||
|
var result = _serviceRecordDataAccess.DeleteServiceRecordById(serviceRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
194
Controllers/Vehicle/SupplyController.cs
Normal file
194
Controllers/Vehicle/SupplyController.cs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
private List<SupplyAvailability> CheckSupplyRecordsAvailability(List<SupplyUsage> supplyUsage)
|
||||||
|
{
|
||||||
|
//returns empty string if all supplies are available
|
||||||
|
var result = new List<SupplyAvailability>();
|
||||||
|
foreach (SupplyUsage supply in supplyUsage)
|
||||||
|
{
|
||||||
|
//get supply record.
|
||||||
|
var supplyData = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
|
||||||
|
if (supplyData == null)
|
||||||
|
{
|
||||||
|
result.Add(new SupplyAvailability { Missing = true });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Add(new SupplyAvailability { Missing = false, Description = supplyData.Description, Required = supply.Quantity, InStock = supplyData.Quantity });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
private List<UploadedFiles> GetSuppliesAttachments(List<SupplyUsage> supplyUsage)
|
||||||
|
{
|
||||||
|
List<UploadedFiles> results = new List<UploadedFiles>();
|
||||||
|
foreach (SupplyUsage supply in supplyUsage)
|
||||||
|
{
|
||||||
|
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
|
||||||
|
results.AddRange(result.Files);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
private List<SupplyUsageHistory> RequisitionSupplyRecordsByUsage(List<SupplyUsage> supplyUsage, DateTime dateRequisitioned, string usageDescription)
|
||||||
|
{
|
||||||
|
List<SupplyUsageHistory> results = new List<SupplyUsageHistory>();
|
||||||
|
foreach (SupplyUsage supply in supplyUsage)
|
||||||
|
{
|
||||||
|
//get supply record.
|
||||||
|
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
|
||||||
|
var unitCost = (result.Quantity != 0) ? result.Cost / result.Quantity : 0;
|
||||||
|
//deduct quantity used.
|
||||||
|
result.Quantity -= supply.Quantity;
|
||||||
|
//deduct cost.
|
||||||
|
result.Cost -= (supply.Quantity * unitCost);
|
||||||
|
//check decimal places to ensure that it always has a max of 3 decimal places.
|
||||||
|
var roundedDecimal = decimal.Round(result.Cost, 3);
|
||||||
|
if (roundedDecimal != result.Cost)
|
||||||
|
{
|
||||||
|
//Too many decimals
|
||||||
|
result.Cost = roundedDecimal;
|
||||||
|
}
|
||||||
|
//create new requisitionrrecord
|
||||||
|
var requisitionRecord = new SupplyUsageHistory
|
||||||
|
{
|
||||||
|
Date = dateRequisitioned,
|
||||||
|
Description = usageDescription,
|
||||||
|
Quantity = supply.Quantity,
|
||||||
|
Cost = (supply.Quantity * unitCost)
|
||||||
|
};
|
||||||
|
result.RequisitionHistory.Add(requisitionRecord);
|
||||||
|
//save
|
||||||
|
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
|
||||||
|
requisitionRecord.Description = result.Description; //change the name of the description for plan/service/repair/upgrade records
|
||||||
|
requisitionRecord.PartNumber = result.PartNumber; //populate part number if not displayed in supplies modal.
|
||||||
|
results.Add(requisitionRecord);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
return PartialView("_SupplyRecords", result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetSupplyRecordsForPlanRecordTemplate(int planRecordTemplateId)
|
||||||
|
{
|
||||||
|
var viewModel = new SupplyUsageViewModel();
|
||||||
|
var planRecordTemplate = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||||
|
if (planRecordTemplate != default && planRecordTemplate.VehicleId != default)
|
||||||
|
{
|
||||||
|
var supplies = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(planRecordTemplate.VehicleId);
|
||||||
|
if (_config.GetServerEnableShopSupplies())
|
||||||
|
{
|
||||||
|
supplies.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0)); // add shop supplies
|
||||||
|
}
|
||||||
|
supplies.RemoveAll(x => x.Quantity <= 0);
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
supplies = supplies.OrderByDescending(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
supplies = supplies.OrderBy(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
viewModel.Supplies = supplies;
|
||||||
|
viewModel.Usage = planRecordTemplate.Supplies;
|
||||||
|
}
|
||||||
|
return PartialView("_SupplyUsage", viewModel);
|
||||||
|
}
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetSupplyRecordsForRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
|
||||||
|
if (_config.GetServerEnableShopSupplies())
|
||||||
|
{
|
||||||
|
result.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0)); // add shop supplies
|
||||||
|
}
|
||||||
|
result.RemoveAll(x => x.Quantity <= 0);
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
var viewModel = new SupplyUsageViewModel
|
||||||
|
{
|
||||||
|
Supplies = result
|
||||||
|
};
|
||||||
|
return PartialView("_SupplyUsage", viewModel);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveSupplyRecordToVehicleId(SupplyRecordInput supplyRecord)
|
||||||
|
{
|
||||||
|
//move files from temp.
|
||||||
|
supplyRecord.Files = supplyRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), supplyRecord.VehicleId, User.Identity.Name, $"{(supplyRecord.Id == default ? "Created" : "Edited")} Supply Record - Description: {supplyRecord.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddSupplyRecordPartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_SupplyRecordModal", new SupplyRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetSupplyRecordForEditById(int supplyRecordId)
|
||||||
|
{
|
||||||
|
var result = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new SupplyRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Description = result.Description,
|
||||||
|
PartNumber = result.PartNumber,
|
||||||
|
Quantity = result.Quantity,
|
||||||
|
PartSupplier = result.PartSupplier,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Files = result.Files,
|
||||||
|
Tags = result.Tags,
|
||||||
|
RequisitionHistory = result.RequisitionHistory,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
return PartialView("_SupplyRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
|
||||||
|
{
|
||||||
|
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(supplyRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
Controllers/Vehicle/TaxController.cs
Normal file
124
Controllers/Vehicle/TaxController.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetTaxRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ToList();
|
||||||
|
}
|
||||||
|
return PartialView("_TaxRecords", result);
|
||||||
|
}
|
||||||
|
private void UpdateRecurringTaxes(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
var recurringFees = result.Where(x => x.IsRecurring);
|
||||||
|
if (recurringFees.Any())
|
||||||
|
{
|
||||||
|
foreach (TaxRecord recurringFee in recurringFees)
|
||||||
|
{
|
||||||
|
var newDate = new DateTime();
|
||||||
|
if (recurringFee.RecurringInterval != ReminderMonthInterval.Other)
|
||||||
|
{
|
||||||
|
newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newDate = recurringFee.Date.AddMonths(recurringFee.CustomMonthInterval);
|
||||||
|
}
|
||||||
|
if (DateTime.Now > newDate)
|
||||||
|
{
|
||||||
|
recurringFee.IsRecurring = false;
|
||||||
|
var newRecurringFee = new TaxRecord()
|
||||||
|
{
|
||||||
|
VehicleId = recurringFee.VehicleId,
|
||||||
|
Date = newDate,
|
||||||
|
Description = recurringFee.Description,
|
||||||
|
Cost = recurringFee.Cost,
|
||||||
|
IsRecurring = true,
|
||||||
|
Notes = recurringFee.Notes,
|
||||||
|
RecurringInterval = recurringFee.RecurringInterval,
|
||||||
|
CustomMonthInterval = recurringFee.CustomMonthInterval,
|
||||||
|
Files = recurringFee.Files,
|
||||||
|
Tags = recurringFee.Tags,
|
||||||
|
ExtraFields = recurringFee.ExtraFields
|
||||||
|
};
|
||||||
|
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
|
||||||
|
_taxRecordDataAccess.SaveTaxRecordToVehicle(newRecurringFee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
|
||||||
|
{
|
||||||
|
//move files from temp.
|
||||||
|
taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
//push back any reminders
|
||||||
|
if (taxRecord.ReminderRecordId.Any())
|
||||||
|
{
|
||||||
|
foreach (int reminderRecordId in taxRecord.ReminderRecordId)
|
||||||
|
{
|
||||||
|
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(taxRecord.Date), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), taxRecord.VehicleId, User.Identity.Name, $"{(taxRecord.Id == default ? "Created" : "Edited")} Tax Record - Description: {taxRecord.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddTaxRecordPartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_TaxRecordModal", new TaxRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetTaxRecordForEditById(int taxRecordId)
|
||||||
|
{
|
||||||
|
var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new TaxRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Description = result.Description,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
IsRecurring = result.IsRecurring,
|
||||||
|
RecurringInterval = result.RecurringInterval,
|
||||||
|
CustomMonthInterval = result.CustomMonthInterval,
|
||||||
|
Files = result.Files,
|
||||||
|
Tags = result.Tags,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
return PartialView("_TaxRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteTaxRecordById(int taxRecordId)
|
||||||
|
{
|
||||||
|
var result = _taxRecordDataAccess.DeleteTaxRecordById(taxRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
Controllers/Vehicle/UpgradeController.cs
Normal file
101
Controllers/Vehicle/UpgradeController.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using CarCareTracker.Filter;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
public partial class VehicleController
|
||||||
|
{
|
||||||
|
[TypeFilter(typeof(CollaboratorFilter))]
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetUpgradeRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
bool _useDescending = _config.GetUserConfig(User).UseDescending;
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
return PartialView("_UpgradeRecords", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
|
||||||
|
{
|
||||||
|
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
|
||||||
|
{
|
||||||
|
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
|
||||||
|
{
|
||||||
|
Date = DateTime.Parse(upgradeRecord.Date),
|
||||||
|
VehicleId = upgradeRecord.VehicleId,
|
||||||
|
Mileage = upgradeRecord.Mileage,
|
||||||
|
Notes = $"Auto Insert From Upgrade Record: {upgradeRecord.Description}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//move files from temp.
|
||||||
|
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
if (upgradeRecord.Supplies.Any())
|
||||||
|
{
|
||||||
|
upgradeRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description);
|
||||||
|
if (upgradeRecord.CopySuppliesAttachment)
|
||||||
|
{
|
||||||
|
upgradeRecord.Files.AddRange(GetSuppliesAttachments(upgradeRecord.Supplies));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//push back any reminders
|
||||||
|
if (upgradeRecord.ReminderRecordId.Any())
|
||||||
|
{
|
||||||
|
foreach (int reminderRecordId in upgradeRecord.ReminderRecordId)
|
||||||
|
{
|
||||||
|
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Mileage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), upgradeRecord.VehicleId, User.Identity.Name, $"{(upgradeRecord.Id == default ? "Created" : "Edited")} Upgrade Record - Description: {upgradeRecord.Description}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddUpgradeRecordPartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_UpgradeRecordModal", new UpgradeRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields });
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new UpgradeRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Description = result.Description,
|
||||||
|
Mileage = result.Mileage,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Files = result.Files,
|
||||||
|
Tags = result.Tags,
|
||||||
|
RequisitionHistory = result.RequisitionHistory,
|
||||||
|
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields)
|
||||||
|
};
|
||||||
|
return PartialView("_UpgradeRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(upgradeRecordId);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");
|
||||||
|
}
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
10
Enum/KioskMode.cs
Normal file
10
Enum/KioskMode.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public enum KioskMode
|
||||||
|
{
|
||||||
|
Vehicle = 0,
|
||||||
|
Plan = 1,
|
||||||
|
Reminder = 2,
|
||||||
|
Cycle = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
using CarCareTracker.Helper;
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
|
||||||
|
|
||||||
namespace CarCareTracker.External.Implementations
|
namespace CarCareTracker.External.Implementations
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
using CarCareTracker.Helper;
|
using CarCareTracker.Helper;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using LiteDB;
|
|
||||||
|
|
||||||
namespace CarCareTracker.External.Implementations
|
namespace CarCareTracker.External.Implementations
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using CarCareTracker.External.Interfaces;
|
using CarCareTracker.External.Interfaces;
|
||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using System.Net.Mail;
|
|
||||||
|
|
||||||
namespace CarCareTracker.External.Implementations
|
namespace CarCareTracker.External.Implementations
|
||||||
{
|
{
|
||||||
@@ -17,7 +16,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, username TEXT not null, emailaddress TEXT not null, password TEXT not null, isadmin BOOLEAN)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, username TEXT not null, emailaddress TEXT not null, password TEXT not null, isadmin BOOLEAN)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//create table if not exist.
|
//create table if not exist.
|
||||||
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)";
|
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)";
|
||||||
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
using (var ctext = pgDataSource.CreateCommand(initCMD))
|
||||||
{
|
{
|
||||||
ctext.ExecuteNonQuery();
|
ctext.ExecuteNonQuery();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace CarCareTracker.Helper
|
namespace CarCareTracker.Helper
|
||||||
{
|
{
|
||||||
@@ -14,6 +15,7 @@ namespace CarCareTracker.Helper
|
|||||||
bool AuthenticateRootUser(string username, string password);
|
bool AuthenticateRootUser(string username, string password);
|
||||||
bool AuthenticateRootUserOIDC(string email);
|
bool AuthenticateRootUserOIDC(string email);
|
||||||
string GetWebHookUrl();
|
string GetWebHookUrl();
|
||||||
|
bool GetCustomWidgetsEnabled();
|
||||||
string GetMOTD();
|
string GetMOTD();
|
||||||
string GetLogoUrl();
|
string GetLogoUrl();
|
||||||
string GetServerLanguage();
|
string GetServerLanguage();
|
||||||
@@ -45,6 +47,10 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
return webhook;
|
return webhook;
|
||||||
}
|
}
|
||||||
|
public bool GetCustomWidgetsEnabled()
|
||||||
|
{
|
||||||
|
return bool.Parse(_config["LUBELOGGER_CUSTOM_WIDGETS"] ?? "false");
|
||||||
|
}
|
||||||
public string GetMOTD()
|
public string GetMOTD()
|
||||||
{
|
{
|
||||||
var motd = _config["LUBELOGGER_MOTD"];
|
var motd = _config["LUBELOGGER_MOTD"];
|
||||||
@@ -141,13 +147,13 @@ namespace CarCareTracker.Helper
|
|||||||
if (!File.Exists(StaticHelper.UserConfigPath))
|
if (!File.Exists(StaticHelper.UserConfigPath))
|
||||||
{
|
{
|
||||||
//if file doesn't exist it might be because it's running on a mounted volume in docker.
|
//if file doesn't exist it might be because it's running on a mounted volume in docker.
|
||||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
|
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(new UserConfig()));
|
||||||
}
|
}
|
||||||
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||||
configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false");
|
configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false");
|
||||||
configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
|
configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
|
||||||
configData.UserPasswordHash = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
|
configData.UserPasswordHash = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
|
||||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData));
|
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(configData));
|
||||||
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -185,6 +191,7 @@ namespace CarCareTracker.Helper
|
|||||||
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
|
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
|
||||||
EnableRootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]),
|
EnableRootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]),
|
||||||
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
|
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
|
||||||
|
AutomaticDecimalFormat = bool.Parse(_config[nameof(UserConfig.AutomaticDecimalFormat)]),
|
||||||
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
|
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
|
||||||
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
|
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
|
||||||
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
|
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
|
||||||
@@ -197,6 +204,7 @@ namespace CarCareTracker.Helper
|
|||||||
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
|
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
|
||||||
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
|
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
|
||||||
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(),
|
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(),
|
||||||
|
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>(),
|
||||||
UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(),
|
UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(),
|
||||||
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
|
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
|
||||||
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)]),
|
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)]),
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ namespace CarCareTracker.Helper
|
|||||||
int ClearTempFolder();
|
int ClearTempFolder();
|
||||||
int ClearUnlinkedThumbnails(List<string> linkedImages);
|
int ClearUnlinkedThumbnails(List<string> linkedImages);
|
||||||
int ClearUnlinkedDocuments(List<string> linkedDocuments);
|
int ClearUnlinkedDocuments(List<string> linkedDocuments);
|
||||||
|
string GetWidgets();
|
||||||
|
bool WidgetsExist();
|
||||||
|
bool SaveWidgets(string widgetsData);
|
||||||
|
bool DeleteWidgets();
|
||||||
}
|
}
|
||||||
public class FileHelper : IFileHelper
|
public class FileHelper : IFileHelper
|
||||||
{
|
{
|
||||||
@@ -100,6 +104,7 @@ namespace CarCareTracker.Helper
|
|||||||
var documentPath = Path.Combine(tempPath, "documents");
|
var documentPath = Path.Combine(tempPath, "documents");
|
||||||
var translationPath = Path.Combine(tempPath, "translations");
|
var translationPath = Path.Combine(tempPath, "translations");
|
||||||
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
||||||
|
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
|
||||||
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
||||||
if (Directory.Exists(imagePath))
|
if (Directory.Exists(imagePath))
|
||||||
{
|
{
|
||||||
@@ -174,6 +179,10 @@ namespace CarCareTracker.Helper
|
|||||||
//data path will always exist as it is created on startup if not.
|
//data path will always exist as it is created on startup if not.
|
||||||
File.Move(dataPath, StaticHelper.DbName, true);
|
File.Move(dataPath, StaticHelper.DbName, true);
|
||||||
}
|
}
|
||||||
|
if (File.Exists(widgetPath))
|
||||||
|
{
|
||||||
|
File.Move(widgetPath, StaticHelper.AdditionalWidgetsPath, true);
|
||||||
|
}
|
||||||
if (File.Exists(configPath))
|
if (File.Exists(configPath))
|
||||||
{
|
{
|
||||||
//check if config folder exists.
|
//check if config folder exists.
|
||||||
@@ -223,6 +232,7 @@ namespace CarCareTracker.Helper
|
|||||||
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||||
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||||
var dataPath = StaticHelper.DbName;
|
var dataPath = StaticHelper.DbName;
|
||||||
|
var widgetPath = StaticHelper.AdditionalWidgetsPath;
|
||||||
var configPath = StaticHelper.UserConfigPath;
|
var configPath = StaticHelper.UserConfigPath;
|
||||||
if (!Directory.Exists(tempPath))
|
if (!Directory.Exists(tempPath))
|
||||||
Directory.CreateDirectory(tempPath);
|
Directory.CreateDirectory(tempPath);
|
||||||
@@ -262,6 +272,12 @@ namespace CarCareTracker.Helper
|
|||||||
Directory.CreateDirectory(newPath);
|
Directory.CreateDirectory(newPath);
|
||||||
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
|
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
|
||||||
}
|
}
|
||||||
|
if (File.Exists(widgetPath))
|
||||||
|
{
|
||||||
|
var newPath = Path.Combine(tempPath, "data");
|
||||||
|
Directory.CreateDirectory(newPath);
|
||||||
|
File.Copy(widgetPath, $"{newPath}/{Path.GetFileName(widgetPath)}");
|
||||||
|
}
|
||||||
if (File.Exists(configPath))
|
if (File.Exists(configPath))
|
||||||
{
|
{
|
||||||
var newPath = Path.Combine(tempPath, "config");
|
var newPath = Path.Combine(tempPath, "config");
|
||||||
@@ -323,12 +339,20 @@ namespace CarCareTracker.Helper
|
|||||||
var tempPath = GetFullFilePath("temp", false);
|
var tempPath = GetFullFilePath("temp", false);
|
||||||
if (Directory.Exists(tempPath))
|
if (Directory.Exists(tempPath))
|
||||||
{
|
{
|
||||||
|
//delete files
|
||||||
var files = Directory.GetFiles(tempPath);
|
var files = Directory.GetFiles(tempPath);
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
filesDeleted++;
|
filesDeleted++;
|
||||||
}
|
}
|
||||||
|
//delete folders
|
||||||
|
var folders = Directory.GetDirectories(tempPath);
|
||||||
|
foreach(var folder in folders)
|
||||||
|
{
|
||||||
|
Directory.Delete(folder, true);
|
||||||
|
filesDeleted++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return filesDeleted;
|
return filesDeleted;
|
||||||
}
|
}
|
||||||
@@ -368,5 +392,57 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
return filesDeleted;
|
return filesDeleted;
|
||||||
}
|
}
|
||||||
|
public string GetWidgets()
|
||||||
|
{
|
||||||
|
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//read file
|
||||||
|
var widgets = File.ReadAllText(StaticHelper.AdditionalWidgetsPath);
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
public bool WidgetsExist()
|
||||||
|
{
|
||||||
|
return File.Exists(StaticHelper.AdditionalWidgetsPath);
|
||||||
|
}
|
||||||
|
public bool SaveWidgets(string widgetsData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Delete Widgets if exists
|
||||||
|
DeleteWidgets();
|
||||||
|
File.WriteAllText(StaticHelper.AdditionalWidgetsPath, widgetsData);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool DeleteWidgets()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||||
|
{
|
||||||
|
File.Delete(StaticHelper.AdditionalWidgetsPath);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ namespace CarCareTracker.Helper
|
|||||||
//need to order by to get correct results
|
//need to order by to get correct results
|
||||||
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
var computedResults = new List<GasRecordViewModel>();
|
var computedResults = new List<GasRecordViewModel>();
|
||||||
int previousMileage = 0;
|
decimal previousMileage = 0.00M;
|
||||||
decimal unFactoredConsumption = 0.00M;
|
decimal unFactoredConsumption = 0.00M;
|
||||||
int unFactoredMileage = 0;
|
decimal unFactoredMileage = 0.00M;
|
||||||
//perform computation.
|
//perform computation.
|
||||||
for (int i = 0; i < result.Count; i++)
|
for (int i = 0; i < result.Count; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using LiteDB;
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
namespace CarCareTracker.Helper;
|
namespace CarCareTracker.Helper;
|
||||||
|
|
||||||
@@ -15,6 +16,29 @@ public class LiteDBHelper: ILiteDBHelper
|
|||||||
if (db == null)
|
if (db == null)
|
||||||
{
|
{
|
||||||
db = new LiteDatabase(StaticHelper.DbName);
|
db = new LiteDatabase(StaticHelper.DbName);
|
||||||
|
if (db.UserVersion == 0)
|
||||||
|
{
|
||||||
|
//migration required to convert ints to decimals
|
||||||
|
var collections = db.GetCollectionNames();
|
||||||
|
foreach (string collection in collections)
|
||||||
|
{
|
||||||
|
var documents = db.GetCollection(collection);
|
||||||
|
foreach (var document in documents.FindAll())
|
||||||
|
{
|
||||||
|
if (document.ContainsKey(nameof(GenericRecord.Mileage)))
|
||||||
|
{
|
||||||
|
document[nameof(GenericRecord.Mileage)] = Convert.ToDecimal(document[nameof(GenericRecord.Mileage)].AsInt32);
|
||||||
|
//check for initial mileage as well
|
||||||
|
if (document.ContainsKey(nameof(OdometerRecord.InitialMileage)))
|
||||||
|
{
|
||||||
|
document[nameof(OdometerRecord.InitialMileage)] = Convert.ToDecimal(document[nameof(OdometerRecord.InitialMileage)].AsInt32);
|
||||||
|
}
|
||||||
|
documents.Update(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.UserVersion = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public LiteDatabase GetLiteDB()
|
public LiteDatabase GetLiteDB()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using MailKit.Net.Smtp;
|
using MailKit.Net.Smtp;
|
||||||
|
using MailKit.Security;
|
||||||
|
|
||||||
namespace CarCareTracker.Helper
|
namespace CarCareTracker.Helper
|
||||||
{
|
{
|
||||||
@@ -109,7 +110,7 @@ namespace CarCareTracker.Helper
|
|||||||
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
|
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
|
||||||
//construct html table.
|
//construct html table.
|
||||||
string emailBody = File.ReadAllText(emailTemplatePath);
|
string emailBody = File.ReadAllText(emailTemplatePath);
|
||||||
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate}");
|
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}");
|
||||||
string tableBody = "";
|
string tableBody = "";
|
||||||
foreach(ReminderRecordViewModel reminder in reminders)
|
foreach(ReminderRecordViewModel reminder in reminders)
|
||||||
{
|
{
|
||||||
@@ -151,7 +152,7 @@ namespace CarCareTracker.Helper
|
|||||||
|
|
||||||
using (var client = new SmtpClient())
|
using (var client = new SmtpClient())
|
||||||
{
|
{
|
||||||
client.Connect(server, mailConfig.Port, MailKit.Security.SecureSocketOptions.Auto);
|
client.Connect(server, mailConfig.Port, SecureSocketOptions.Auto);
|
||||||
//perform authentication if either username or password is provided.
|
//perform authentication if either username or password is provided.
|
||||||
//do not perform authentication if neither are provided.
|
//do not perform authentication if neither are provided.
|
||||||
if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) {
|
if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
public interface IReminderHelper
|
public interface IReminderHelper
|
||||||
{
|
{
|
||||||
ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, int? currentMileage);
|
ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, decimal? currentMileage);
|
||||||
List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare);
|
List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, decimal currentMileage, DateTime dateCompare);
|
||||||
}
|
}
|
||||||
public class ReminderHelper: IReminderHelper
|
public class ReminderHelper: IReminderHelper
|
||||||
{
|
{
|
||||||
@@ -14,7 +14,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, int? currentMileage)
|
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, decimal? currentMileage)
|
||||||
{
|
{
|
||||||
var newDate = currentDate ?? existingReminder.Date;
|
var newDate = currentDate ?? existingReminder.Date;
|
||||||
var newMileage = currentMileage ?? existingReminder.Mileage;
|
var newMileage = currentMileage ?? existingReminder.Mileage;
|
||||||
@@ -60,7 +60,7 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
return existingReminder;
|
return existingReminder;
|
||||||
}
|
}
|
||||||
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare)
|
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, decimal currentMileage, DateTime dateCompare)
|
||||||
{
|
{
|
||||||
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
||||||
var reminderUrgencyConfig = _config.GetReminderUrgencyConfig();
|
var reminderUrgencyConfig = _config.GetReminderUrgencyConfig();
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ namespace CarCareTracker.Helper
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StaticHelper
|
public static class StaticHelper
|
||||||
{
|
{
|
||||||
public static string VersionNumber = "1.3.8";
|
public static string VersionNumber = "1.4.0";
|
||||||
public static string DbName = "data/cartracker.db";
|
public static string DbName = "data/cartracker.db";
|
||||||
public static string UserConfigPath = "config/userConfig.json";
|
public static string UserConfigPath = "config/userConfig.json";
|
||||||
|
public static string AdditionalWidgetsPath = "data/widgets.html";
|
||||||
public static string GenericErrorMessage = "An error occurred, please try again later";
|
public static string GenericErrorMessage = "An error occurred, please try again later";
|
||||||
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
||||||
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
||||||
public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
|
public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
|
||||||
|
public static string TranslationPath = "https://hargata.github.io/lubelog_translations";
|
||||||
|
public static string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
|
||||||
|
public const string ReportNote = "Report generated by LubeLogger, a Free and Open Source Vehicle Maintenance Tracker - LubeLogger.com";
|
||||||
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
||||||
{
|
{
|
||||||
switch (input)
|
switch (input)
|
||||||
@@ -30,6 +34,66 @@ namespace CarCareTracker.Helper
|
|||||||
return input.ToString();
|
return input.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static string GetTitleCaseReminderUrgency(string input)
|
||||||
|
{
|
||||||
|
switch (input)
|
||||||
|
{
|
||||||
|
case "NotUrgent":
|
||||||
|
return "Not Urgent";
|
||||||
|
case "VeryUrgent":
|
||||||
|
return "Very Urgent";
|
||||||
|
case "PastDue":
|
||||||
|
return "Past Due";
|
||||||
|
default:
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static string GetReminderUrgencyColor(ReminderUrgency input)
|
||||||
|
{
|
||||||
|
switch (input)
|
||||||
|
{
|
||||||
|
case ReminderUrgency.NotUrgent:
|
||||||
|
return "text-bg-success";
|
||||||
|
case ReminderUrgency.VeryUrgent:
|
||||||
|
return "text-bg-danger";
|
||||||
|
case ReminderUrgency.PastDue:
|
||||||
|
return "text-bg-secondary";
|
||||||
|
default:
|
||||||
|
return "text-bg-warning";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPlanRecordColor(PlanPriority input)
|
||||||
|
{
|
||||||
|
switch (input)
|
||||||
|
{
|
||||||
|
case PlanPriority.Critical:
|
||||||
|
return "text-bg-danger";
|
||||||
|
case PlanPriority.Normal:
|
||||||
|
return "text-bg-primary";
|
||||||
|
case PlanPriority.Low:
|
||||||
|
return "text-bg-info";
|
||||||
|
default:
|
||||||
|
return "text-bg-primary";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPlanRecordProgress(PlanProgress input)
|
||||||
|
{
|
||||||
|
switch (input)
|
||||||
|
{
|
||||||
|
case PlanProgress.Backlog:
|
||||||
|
return "Planned";
|
||||||
|
case PlanProgress.InProgress:
|
||||||
|
return "Doing";
|
||||||
|
case PlanProgress.Testing:
|
||||||
|
return "Testing";
|
||||||
|
case PlanProgress.Done:
|
||||||
|
return "Done";
|
||||||
|
default:
|
||||||
|
return input.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string TruncateStrings(string input, int maxLength = 25)
|
public static string TruncateStrings(string input, int maxLength = 25)
|
||||||
{
|
{
|
||||||
@@ -119,6 +183,10 @@ namespace CarCareTracker.Helper
|
|||||||
new CostForVehicleByMonth { MonthId = 12, Cost = 0M}
|
new CostForVehicleByMonth { MonthId = 12, Cost = 0M}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
public static List<string> GetBarChartColors()
|
||||||
|
{
|
||||||
|
return new List<string> { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
||||||
|
}
|
||||||
|
|
||||||
public static ServiceRecord GenericToServiceRecord(GenericRecord input)
|
public static ServiceRecord GenericToServiceRecord(GenericRecord input)
|
||||||
{
|
{
|
||||||
@@ -199,6 +267,8 @@ namespace CarCareTracker.Helper
|
|||||||
recordExtraFields.Add(extraField);
|
recordExtraFields.Add(extraField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//re-order extra fields
|
||||||
|
recordExtraFields = recordExtraFields.OrderBy(x => templateExtraFields.FindIndex(y => y.Name == x.Name)).ToList();
|
||||||
return recordExtraFields;
|
return recordExtraFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +314,10 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
var motd = config["LUBELOGGER_MOTD"] ?? "Not Configured";
|
var motd = config["LUBELOGGER_MOTD"] ?? "Not Configured";
|
||||||
Console.WriteLine($"Message Of The Day: {motd}");
|
Console.WriteLine($"Message Of The Day: {motd}");
|
||||||
|
if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name))
|
||||||
|
{
|
||||||
|
Console.WriteLine("No Locale or Culture Configured for LubeLogger, Check Environment Variables");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
|
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
|
||||||
{
|
{
|
||||||
@@ -288,6 +362,85 @@ namespace CarCareTracker.Helper
|
|||||||
return "bi-file-bar-graph";
|
return "bi-file-bar-graph";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static string GetVehicleIdentifier(Vehicle vehicle)
|
||||||
|
{
|
||||||
|
if (vehicle.VehicleIdentifier == "LicensePlate")
|
||||||
|
{
|
||||||
|
return vehicle.LicensePlate;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
if (vehicle.ExtraFields.Any(x=>x.Name == vehicle.VehicleIdentifier))
|
||||||
|
{
|
||||||
|
return vehicle.ExtraFields?.FirstOrDefault(x=>x.Name == vehicle.VehicleIdentifier)?.Value;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static string GetVehicleIdentifier(VehicleViewModel vehicle)
|
||||||
|
{
|
||||||
|
if (vehicle.VehicleIdentifier == "LicensePlate")
|
||||||
|
{
|
||||||
|
return vehicle.LicensePlate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (vehicle.ExtraFields.Any(x => x.Name == vehicle.VehicleIdentifier))
|
||||||
|
{
|
||||||
|
return vehicle.ExtraFields?.FirstOrDefault(x => x.Name == vehicle.VehicleIdentifier)?.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Translations
|
||||||
|
public static string GetTranslationDownloadPath(string continent, string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name)){
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (continent)
|
||||||
|
{
|
||||||
|
case "NorthAmerica":
|
||||||
|
continent = "North America";
|
||||||
|
break;
|
||||||
|
case "SouthAmerica":
|
||||||
|
continent = "South America";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $"{TranslationPath}/{continent}/{name}.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static string GetTranslationName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
|
||||||
|
string displayName = CultureInfo.GetCultureInfo(cleanedName).DisplayName;
|
||||||
|
if (string.IsNullOrWhiteSpace(displayName))
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
//CSV Write Methods
|
//CSV Write Methods
|
||||||
public static void WriteGenericRecordExportModel(CsvWriter _csv, IEnumerable<GenericRecordExportModel> genericRecords)
|
public static void WriteGenericRecordExportModel(CsvWriter _csv, IEnumerable<GenericRecordExportModel> genericRecords)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace CarCareTracker.Helper
|
namespace CarCareTracker.Helper
|
||||||
@@ -6,17 +7,22 @@ namespace CarCareTracker.Helper
|
|||||||
public interface ITranslationHelper
|
public interface ITranslationHelper
|
||||||
{
|
{
|
||||||
string Translate(string userLanguage, string text);
|
string Translate(string userLanguage, string text);
|
||||||
|
Dictionary<string, string> GetTranslations(string userLanguage);
|
||||||
|
OperationResponse SaveTranslation(string userLanguage, Dictionary<string, string> translations);
|
||||||
|
string ExportTranslation(Dictionary<string, string> translations);
|
||||||
}
|
}
|
||||||
public class TranslationHelper : ITranslationHelper
|
public class TranslationHelper : ITranslationHelper
|
||||||
{
|
{
|
||||||
private readonly IFileHelper _fileHelper;
|
private readonly IFileHelper _fileHelper;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly ILogger<ITranslationHelper> _logger;
|
||||||
private IMemoryCache _cache;
|
private IMemoryCache _cache;
|
||||||
public TranslationHelper(IFileHelper fileHelper, IConfiguration config, IMemoryCache memoryCache)
|
public TranslationHelper(IFileHelper fileHelper, IConfiguration config, IMemoryCache memoryCache, ILogger<ITranslationHelper> logger)
|
||||||
{
|
{
|
||||||
_fileHelper = fileHelper;
|
_fileHelper = fileHelper;
|
||||||
_config = config;
|
_config = config;
|
||||||
_cache = memoryCache;
|
_cache = memoryCache;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
public string Translate(string userLanguage, string text)
|
public string Translate(string userLanguage, string text)
|
||||||
{
|
{
|
||||||
@@ -36,11 +42,13 @@ namespace CarCareTracker.Helper
|
|||||||
return translationDictionary ?? new Dictionary<string, string>();
|
return translationDictionary ?? new Dictionary<string, string>();
|
||||||
} catch (Exception ex)
|
} catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
return new Dictionary<string, string>();
|
return new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_logger.LogError($"Could not find translation file for {userLanguage}");
|
||||||
return new Dictionary<string, string>();
|
return new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -52,10 +60,138 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
//create entry
|
//create entry
|
||||||
dictionary.Add(translationKey, text);
|
dictionary.Add(translationKey, text);
|
||||||
|
_logger.LogInformation($"Translation key added to {userLanguage} for {translationKey} with value {text}");
|
||||||
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(dictionary));
|
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(dictionary));
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
private Dictionary<string, string> GetDefaultTranslation()
|
||||||
|
{
|
||||||
|
//this method always returns en_US translation.
|
||||||
|
var translationFilePath = _fileHelper.GetFullFilePath($"/defaults/en_US.json");
|
||||||
|
if (!string.IsNullOrWhiteSpace(translationFilePath))
|
||||||
|
{
|
||||||
|
//file exists.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationFile = File.ReadAllText(translationFilePath);
|
||||||
|
var translationDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(translationFile);
|
||||||
|
return translationDictionary ?? new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_logger.LogError($"Could not find translation file for en_US");
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
public Dictionary<string, string> GetTranslations(string userLanguage)
|
||||||
|
{
|
||||||
|
var defaultTranslation = GetDefaultTranslation();
|
||||||
|
if (userLanguage == "en_US")
|
||||||
|
{
|
||||||
|
return defaultTranslation;
|
||||||
|
}
|
||||||
|
var translationFilePath = _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json");
|
||||||
|
if (!string.IsNullOrWhiteSpace(translationFilePath))
|
||||||
|
{
|
||||||
|
//file exists.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var translationFile = File.ReadAllText(translationFilePath);
|
||||||
|
var translationDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(translationFile);
|
||||||
|
if (translationDictionary != null)
|
||||||
|
{
|
||||||
|
foreach(var translation in translationDictionary)
|
||||||
|
{
|
||||||
|
if (defaultTranslation.ContainsKey(translation.Key))
|
||||||
|
{
|
||||||
|
defaultTranslation[translation.Key] = translation.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultTranslation ?? new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_logger.LogError($"Could not find translation file for {userLanguage}");
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
public OperationResponse SaveTranslation(string userLanguage, Dictionary<string, string> translations)
|
||||||
|
{
|
||||||
|
bool create = bool.Parse(_config["LUBELOGGER_TRANSLATOR"] ?? "false");
|
||||||
|
bool isDefaultLanguage = userLanguage == "en_US";
|
||||||
|
if (isDefaultLanguage && !create)
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." };
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(userLanguage))
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "File name is not provided." };
|
||||||
|
}
|
||||||
|
if (!translations.Any())
|
||||||
|
{
|
||||||
|
return new OperationResponse { Success = false, Message = "Translation has no data." };
|
||||||
|
}
|
||||||
|
var translationFilePath = isDefaultLanguage ? _fileHelper.GetFullFilePath($"/defaults/en_US.json") : _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(translationFilePath))
|
||||||
|
{
|
||||||
|
//write to file
|
||||||
|
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(translations));
|
||||||
|
_cache.Remove($"lang_{userLanguage}"); //clear out cache, force a reload from file.
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
//check if directory exists first.
|
||||||
|
var translationDirectory = _fileHelper.GetFullFilePath("translations/", false);
|
||||||
|
if (!Directory.Exists(translationDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(translationDirectory);
|
||||||
|
}
|
||||||
|
//write to file
|
||||||
|
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(translations));
|
||||||
|
}
|
||||||
|
return new OperationResponse { Success = true, Message = "Translation Updated" };
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string ExportTranslation(Dictionary<string, string> translations)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tempFileName = $"/temp/{Guid.NewGuid()}.json";
|
||||||
|
string uploadDirectory = _fileHelper.GetFullFilePath("temp/", false);
|
||||||
|
if (!Directory.Exists(uploadDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(uploadDirectory);
|
||||||
|
}
|
||||||
|
var saveFilePath = _fileHelper.GetFullFilePath(tempFileName, false);
|
||||||
|
//standardize translation format for export only.
|
||||||
|
Dictionary<string, string> sortedTranslations = new Dictionary<string, string>();
|
||||||
|
foreach (var translation in translations.OrderBy(x => x.Key))
|
||||||
|
{
|
||||||
|
sortedTranslations.Add(translation.Key, translation.Value);
|
||||||
|
};
|
||||||
|
File.WriteAllText(saveFilePath, JsonSerializer.Serialize(sortedTranslations, new JsonSerializerOptions { WriteIndented = true }));
|
||||||
|
return tempFileName;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex.Message);
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace CarCareTracker.Logic
|
|||||||
{
|
{
|
||||||
public interface IOdometerLogic
|
public interface IOdometerLogic
|
||||||
{
|
{
|
||||||
int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords);
|
decimal GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords);
|
||||||
bool AutoInsertOdometerRecord(OdometerRecord odometer);
|
bool AutoInsertOdometerRecord(OdometerRecord odometer);
|
||||||
List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords);
|
List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords);
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ namespace CarCareTracker.Logic
|
|||||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
public int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords)
|
public decimal GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords)
|
||||||
{
|
{
|
||||||
if (!odometerRecords.Any())
|
if (!odometerRecords.Any())
|
||||||
{
|
{
|
||||||
@@ -47,7 +47,7 @@ namespace CarCareTracker.Logic
|
|||||||
{
|
{
|
||||||
//perform ordering
|
//perform ordering
|
||||||
odometerRecords = odometerRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
odometerRecords = odometerRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
int previousMileage = 0;
|
decimal previousMileage = 0.00M;
|
||||||
for (int i = 0; i < odometerRecords.Count; i++)
|
for (int i = 0; i < odometerRecords.Count; i++)
|
||||||
{
|
{
|
||||||
var currentObject = odometerRecords[i];
|
var currentObject = odometerRecords[i];
|
||||||
|
|||||||
@@ -8,12 +8,15 @@ namespace CarCareTracker.Logic
|
|||||||
{
|
{
|
||||||
VehicleRecords GetVehicleRecords(int vehicleId);
|
VehicleRecords GetVehicleRecords(int vehicleId);
|
||||||
decimal GetVehicleTotalCost(VehicleRecords vehicleRecords);
|
decimal GetVehicleTotalCost(VehicleRecords vehicleRecords);
|
||||||
int GetMaxMileage(int vehicleId);
|
decimal GetMaxMileage(int vehicleId);
|
||||||
int GetMaxMileage(VehicleRecords vehicleRecords);
|
decimal GetMaxMileage(VehicleRecords vehicleRecords);
|
||||||
int GetMinMileage(int vehicleId);
|
decimal GetMinMileage(int vehicleId);
|
||||||
int GetMinMileage(VehicleRecords vehicleRecords);
|
decimal GetMinMileage(VehicleRecords vehicleRecords);
|
||||||
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
|
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
|
||||||
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
|
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, decimal currentMileage);
|
||||||
|
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
|
||||||
|
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
|
||||||
|
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
|
||||||
}
|
}
|
||||||
public class VehicleLogic: IVehicleLogic
|
public class VehicleLogic: IVehicleLogic
|
||||||
{
|
{
|
||||||
@@ -24,6 +27,7 @@ namespace CarCareTracker.Logic
|
|||||||
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
||||||
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
||||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
|
private readonly IPlanRecordDataAccess _planRecordDataAccess;
|
||||||
private readonly IReminderHelper _reminderHelper;
|
private readonly IReminderHelper _reminderHelper;
|
||||||
public VehicleLogic(
|
public VehicleLogic(
|
||||||
IServiceRecordDataAccess serviceRecordDataAccess,
|
IServiceRecordDataAccess serviceRecordDataAccess,
|
||||||
@@ -33,6 +37,7 @@ namespace CarCareTracker.Logic
|
|||||||
ITaxRecordDataAccess taxRecordDataAccess,
|
ITaxRecordDataAccess taxRecordDataAccess,
|
||||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
|
IPlanRecordDataAccess planRecordDataAccess,
|
||||||
IReminderHelper reminderHelper
|
IReminderHelper reminderHelper
|
||||||
) {
|
) {
|
||||||
_serviceRecordDataAccess = serviceRecordDataAccess;
|
_serviceRecordDataAccess = serviceRecordDataAccess;
|
||||||
@@ -41,6 +46,7 @@ namespace CarCareTracker.Logic
|
|||||||
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||||
_taxRecordDataAccess = taxRecordDataAccess;
|
_taxRecordDataAccess = taxRecordDataAccess;
|
||||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||||
|
_planRecordDataAccess = planRecordDataAccess;
|
||||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||||
_reminderHelper = reminderHelper;
|
_reminderHelper = reminderHelper;
|
||||||
}
|
}
|
||||||
@@ -65,9 +71,9 @@ namespace CarCareTracker.Logic
|
|||||||
var gasRecordSum = vehicleRecords.GasRecords.Sum(x => x.Cost);
|
var gasRecordSum = vehicleRecords.GasRecords.Sum(x => x.Cost);
|
||||||
return serviceRecordSum + repairRecordSum + upgradeRecordSum + taxRecordSum + gasRecordSum;
|
return serviceRecordSum + repairRecordSum + upgradeRecordSum + taxRecordSum + gasRecordSum;
|
||||||
}
|
}
|
||||||
public int GetMaxMileage(int vehicleId)
|
public decimal GetMaxMileage(int vehicleId)
|
||||||
{
|
{
|
||||||
var numbersArray = new List<int>();
|
var numbersArray = new List<decimal>();
|
||||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
if (serviceRecords.Any())
|
if (serviceRecords.Any())
|
||||||
{
|
{
|
||||||
@@ -93,11 +99,11 @@ namespace CarCareTracker.Logic
|
|||||||
{
|
{
|
||||||
numbersArray.Add(odometerRecords.Max(x => x.Mileage));
|
numbersArray.Add(odometerRecords.Max(x => x.Mileage));
|
||||||
}
|
}
|
||||||
return numbersArray.Any() ? numbersArray.Max() : 0;
|
return numbersArray.Any() ? numbersArray.Max() : 0.00M;
|
||||||
}
|
}
|
||||||
public int GetMaxMileage(VehicleRecords vehicleRecords)
|
public decimal GetMaxMileage(VehicleRecords vehicleRecords)
|
||||||
{
|
{
|
||||||
var numbersArray = new List<int>();
|
var numbersArray = new List<decimal>();
|
||||||
if (vehicleRecords.ServiceRecords.Any())
|
if (vehicleRecords.ServiceRecords.Any())
|
||||||
{
|
{
|
||||||
numbersArray.Add(vehicleRecords.ServiceRecords.Max(x => x.Mileage));
|
numbersArray.Add(vehicleRecords.ServiceRecords.Max(x => x.Mileage));
|
||||||
@@ -120,9 +126,9 @@ namespace CarCareTracker.Logic
|
|||||||
}
|
}
|
||||||
return numbersArray.Any() ? numbersArray.Max() : 0;
|
return numbersArray.Any() ? numbersArray.Max() : 0;
|
||||||
}
|
}
|
||||||
public int GetMinMileage(int vehicleId)
|
public decimal GetMinMileage(int vehicleId)
|
||||||
{
|
{
|
||||||
var numbersArray = new List<int>();
|
var numbersArray = new List<decimal>();
|
||||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
|
||||||
if (serviceRecords.Any())
|
if (serviceRecords.Any())
|
||||||
{
|
{
|
||||||
@@ -150,9 +156,9 @@ namespace CarCareTracker.Logic
|
|||||||
}
|
}
|
||||||
return numbersArray.Any() ? numbersArray.Min() : 0;
|
return numbersArray.Any() ? numbersArray.Min() : 0;
|
||||||
}
|
}
|
||||||
public int GetMinMileage(VehicleRecords vehicleRecords)
|
public decimal GetMinMileage(VehicleRecords vehicleRecords)
|
||||||
{
|
{
|
||||||
var numbersArray = new List<int>();
|
var numbersArray = new List<decimal>();
|
||||||
var _serviceRecords = vehicleRecords.ServiceRecords.Where(x => x.Mileage != default).ToList();
|
var _serviceRecords = vehicleRecords.ServiceRecords.Where(x => x.Mileage != default).ToList();
|
||||||
if (_serviceRecords.Any())
|
if (_serviceRecords.Any())
|
||||||
{
|
{
|
||||||
@@ -212,11 +218,104 @@ namespace CarCareTracker.Logic
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage)
|
public bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, decimal currentMileage)
|
||||||
{
|
{
|
||||||
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||||
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
|
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
|
||||||
return results.Any(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
|
return results.Any(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles)
|
||||||
|
{
|
||||||
|
List<VehicleInfo> apiResult = new List<VehicleInfo>();
|
||||||
|
|
||||||
|
foreach (Vehicle vehicle in vehicles)
|
||||||
|
{
|
||||||
|
var currentMileage = GetMaxMileage(vehicle.Id);
|
||||||
|
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
||||||
|
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
|
||||||
|
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id);
|
||||||
|
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id);
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id);
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id);
|
||||||
|
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id);
|
||||||
|
var planRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
|
||||||
|
|
||||||
|
var resultToAdd = new VehicleInfo()
|
||||||
|
{
|
||||||
|
VehicleData = vehicle,
|
||||||
|
LastReportedOdometer = currentMileage,
|
||||||
|
ServiceRecordCount = serviceRecords.Count(),
|
||||||
|
ServiceRecordCost = serviceRecords.Sum(x => x.Cost),
|
||||||
|
RepairRecordCount = repairRecords.Count(),
|
||||||
|
RepairRecordCost = repairRecords.Sum(x => x.Cost),
|
||||||
|
UpgradeRecordCount = upgradeRecords.Count(),
|
||||||
|
UpgradeRecordCost = upgradeRecords.Sum(x => x.Cost),
|
||||||
|
GasRecordCount = gasRecords.Count(),
|
||||||
|
GasRecordCost = gasRecords.Sum(x => x.Cost),
|
||||||
|
TaxRecordCount = taxRecords.Count(),
|
||||||
|
TaxRecordCost = taxRecords.Sum(x => x.Cost),
|
||||||
|
VeryUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.VeryUrgent),
|
||||||
|
PastDueReminderCount = results.Count(x => x.Urgency == ReminderUrgency.PastDue),
|
||||||
|
UrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.Urgent),
|
||||||
|
NotUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.NotUrgent),
|
||||||
|
PlanRecordBackLogCount = planRecords.Count(x => x.Progress == PlanProgress.Backlog),
|
||||||
|
PlanRecordInProgressCount = planRecords.Count(x => x.Progress == PlanProgress.InProgress),
|
||||||
|
PlanRecordTestingCount = planRecords.Count(x => x.Progress == PlanProgress.Testing),
|
||||||
|
PlanRecordDoneCount = planRecords.Count(x => x.Progress == PlanProgress.Done)
|
||||||
|
};
|
||||||
|
//set next reminder
|
||||||
|
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
|
||||||
|
{
|
||||||
|
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
||||||
|
}
|
||||||
|
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
|
||||||
|
{
|
||||||
|
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
|
||||||
|
}
|
||||||
|
apiResult.Add(resultToAdd);
|
||||||
|
}
|
||||||
|
return apiResult;
|
||||||
|
}
|
||||||
|
public List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar)
|
||||||
|
{
|
||||||
|
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
|
||||||
|
foreach (Vehicle vehicle in vehicles)
|
||||||
|
{
|
||||||
|
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
|
||||||
|
if (isCalendar)
|
||||||
|
{
|
||||||
|
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
|
||||||
|
//we don't care about mileages so we can basically fake the current vehicle mileage.
|
||||||
|
}
|
||||||
|
if (vehicleReminders.Any())
|
||||||
|
{
|
||||||
|
var vehicleMileage = isCalendar ? 0 : GetMaxMileage(vehicle.Id);
|
||||||
|
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, vehicleMileage, DateTime.Now);
|
||||||
|
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Metric = x.Metric, Date = x.Date, Notes = x.Notes, Mileage = x.Mileage, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" }).ToList();
|
||||||
|
reminders.AddRange(reminderUrgency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reminders.OrderByDescending(x=>x.Urgency).ToList();
|
||||||
|
}
|
||||||
|
public List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone)
|
||||||
|
{
|
||||||
|
List<PlanRecord> plans = new List<PlanRecord>();
|
||||||
|
foreach (Vehicle vehicle in vehicles)
|
||||||
|
{
|
||||||
|
var vehiclePlans = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
|
||||||
|
if (excludeDone)
|
||||||
|
{
|
||||||
|
vehiclePlans.RemoveAll(x => x.Progress == PlanProgress.Done);
|
||||||
|
}
|
||||||
|
if (vehiclePlans.Any())
|
||||||
|
{
|
||||||
|
var convertedPlans = vehiclePlans.Select(x => new PlanRecord { Priority = x.Priority, Progress = x.Progress, Notes = x.Notes, RequisitionHistory = x.RequisitionHistory, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" });
|
||||||
|
plans.AddRange(convertedPlans);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication;
|
|||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ namespace CarCareTracker.Middleware
|
|||||||
new(ClaimTypes.Role, nameof(UserData.IsRootUser))
|
new(ClaimTypes.Role, nameof(UserData.IsRootUser))
|
||||||
};
|
};
|
||||||
appIdentity.AddClaims(userIdentity);
|
appIdentity.AddClaims(userIdentity);
|
||||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||||
return AuthenticateResult.Success(ticket);
|
return AuthenticateResult.Success(ticket);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -59,7 +60,7 @@ namespace CarCareTracker.Middleware
|
|||||||
{
|
{
|
||||||
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
||||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||||
string decodedString = System.Text.Encoding.UTF8.GetString(data);
|
string decodedString = Encoding.UTF8.GetString(data);
|
||||||
var splitString = decodedString.Split(":");
|
var splitString = decodedString.Split(":");
|
||||||
if (splitString.Count() != 2)
|
if (splitString.Count() != 2)
|
||||||
{
|
{
|
||||||
@@ -85,7 +86,7 @@ namespace CarCareTracker.Middleware
|
|||||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||||
}
|
}
|
||||||
appIdentity.AddClaims(userIdentity);
|
appIdentity.AddClaims(userIdentity);
|
||||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||||
return AuthenticateResult.Success(ticket);
|
return AuthenticateResult.Success(ticket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +134,7 @@ namespace CarCareTracker.Middleware
|
|||||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||||
}
|
}
|
||||||
appIdentity.AddClaims(userIdentity);
|
appIdentity.AddClaims(userIdentity);
|
||||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||||
return AuthenticateResult.Success(ticket);
|
return AuthenticateResult.Success(ticket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
public decimal TaxRecordCost { get; set; }
|
public decimal TaxRecordCost { get; set; }
|
||||||
public int GasRecordCount { get; set; }
|
public int GasRecordCount { get; set; }
|
||||||
public decimal GasRecordCost { get; set; }
|
public decimal GasRecordCost { get; set; }
|
||||||
public int LastReportedOdometer { get; set; }
|
public decimal LastReportedOdometer { get; set; }
|
||||||
public int PlanRecordBackLogCount { get; set; }
|
public int PlanRecordBackLogCount { get; set; }
|
||||||
public int PlanRecordInProgressCount { get; set; }
|
public int PlanRecordInProgressCount { get; set; }
|
||||||
public int PlanRecordTestingCount { get; set; }
|
public int PlanRecordTestingCount { get; set; }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public List<int> ReminderRecordId { get; set; } = new List<int>();
|
public List<int> ReminderRecordId { get; set; } = new List<int>();
|
||||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// American moment
|
/// American moment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wtf is a kilometer?
|
/// Wtf is a kilometer?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// American moment
|
/// American moment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wtf is a kilometer?
|
/// Wtf is a kilometer?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// American moment
|
/// American moment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wtf is a kilometer?
|
/// Wtf is a kilometer?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal Gallons { get; set; }
|
public decimal Gallons { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public int DeltaMileage { get; set; }
|
public decimal DeltaMileage { get; set; }
|
||||||
public decimal MilesPerGallon { get; set; }
|
public decimal MilesPerGallon { get; set; }
|
||||||
public decimal CostPerGallon { get; set; }
|
public decimal CostPerGallon { get; set; }
|
||||||
public bool IsFillToFull { get; set; }
|
public bool IsFillToFull { get; set; }
|
||||||
|
|||||||
14
Models/Kiosk/KioskViewModel.cs
Normal file
14
Models/Kiosk/KioskViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class KioskViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List of vehicle ids to exclude from Kiosk Dashboard
|
||||||
|
/// </summary>
|
||||||
|
public List<int> Exclusions { get; set; } = new List<int>();
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to retrieve data for vehicle, plans, or reminder view.
|
||||||
|
/// </summary>
|
||||||
|
public KioskMode KioskMode { get; set; } = KioskMode.Vehicle;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public int InitialMileage { get; set; }
|
public decimal InitialMileage { get; set; }
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public int DistanceTraveled { get { return Mileage - InitialMileage; } }
|
public decimal DistanceTraveled { get { return Mileage - InitialMileage; } }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||||
public int InitialMileage { get; set; }
|
public decimal InitialMileage { get; set; }
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public bool IsRecurring { get; set; } = false;
|
public bool IsRecurring { get; set; } = false;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public string Date { get; set; } = DateTime.Now.AddDays(1).ToShortDateString();
|
public string Date { get; set; } = DateTime.Now.AddDays(1).ToShortDateString();
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public bool IsRecurring { get; set; } = false;
|
public bool IsRecurring { get; set; } = false;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
public int MonthId { get; set; }
|
public int MonthId { get; set; }
|
||||||
public string MonthName { get; set; }
|
public string MonthName { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public int DistanceTraveled { get; set; }
|
public decimal DistanceTraveled { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
public class CostTableForVehicle
|
public class CostTableForVehicle
|
||||||
{
|
{
|
||||||
public string DistanceUnit { get; set; } = "Cost Per Mile";
|
public string DistanceUnit { get; set; } = "Cost Per Mile";
|
||||||
public int TotalDistance { get; set; }
|
public decimal TotalDistance { get; set; }
|
||||||
public int NumberOfDays { get; set; }
|
public int NumberOfDays { get; set; }
|
||||||
public decimal ServiceRecordSum { get; set; }
|
public decimal ServiceRecordSum { get; set; }
|
||||||
public decimal GasRecordSum { get; set; }
|
public decimal GasRecordSum { get; set; }
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
{
|
{
|
||||||
public ImportMode DataType { get; set; }
|
public ImportMode DataType { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public int Odometer { get; set; }
|
public decimal Odometer { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
Models/Report/MPGForVehicleByMonth.cs
Normal file
9
Models/Report/MPGForVehicleByMonth.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class MPGForVehicleByMonth
|
||||||
|
{
|
||||||
|
public List<CostForVehicleByMonth> CostData { get; set; } = new List<CostForVehicleByMonth>();
|
||||||
|
public List<CostForVehicleByMonth> SortedCostData { get; set; } = new List<CostForVehicleByMonth>();
|
||||||
|
public string Unit { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Models/Report/ReportParameter.cs
Normal file
8
Models/Report/ReportParameter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class ReportParameter
|
||||||
|
{
|
||||||
|
public List<string> VisibleColumns { get; set; } = new List<string>();
|
||||||
|
public List<string> ExtraFields { get; set; } = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@
|
|||||||
public class ReportViewModel
|
public class ReportViewModel
|
||||||
{
|
{
|
||||||
public List<CostForVehicleByMonth> CostForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
|
public List<CostForVehicleByMonth> CostForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
|
||||||
public List<CostForVehicleByMonth> FuelMileageForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
|
public MPGForVehicleByMonth FuelMileageForVehicleByMonth { get; set; } = new MPGForVehicleByMonth();
|
||||||
public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
|
public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
|
||||||
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
|
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
|
||||||
public List<int> Years { get; set; } = new List<int>();
|
public List<int> Years { get; set; } = new List<int>();
|
||||||
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>();
|
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>();
|
||||||
|
public bool CustomWidgetsConfigured { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
{
|
{
|
||||||
public Vehicle VehicleData { get; set; }
|
public Vehicle VehicleData { get; set; }
|
||||||
public List<GenericReportModel> VehicleHistory { get; set; }
|
public List<GenericReportModel> VehicleHistory { get; set; }
|
||||||
|
public ReportParameter ReportParameters { get; set; }
|
||||||
public string Odometer { get; set; }
|
public string Odometer { get; set; }
|
||||||
public string MPG { get; set; }
|
public string MPG { get; set; }
|
||||||
public decimal TotalCost { get; set; }
|
public decimal TotalCost { get; set; }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public List<int> ReminderRecordId { get; set; } = new List<int>();
|
public List<int> ReminderRecordId { get; set; } = new List<int>();
|
||||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
|||||||
@@ -4,6 +4,5 @@ namespace CarCareTracker.Models
|
|||||||
{
|
{
|
||||||
public UserConfig UserConfig { get; set; }
|
public UserConfig UserConfig { get; set; }
|
||||||
public List<string> UILanguages { get; set; }
|
public List<string> UILanguages { get; set; }
|
||||||
public Sponsors Sponsors { get; set; } = new Sponsors();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
|||||||
11
Models/Supply/SupplyAvailability.cs
Normal file
11
Models/Supply/SupplyAvailability.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class SupplyAvailability
|
||||||
|
{
|
||||||
|
public bool Missing { get; set; }
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public decimal Required { get; set; }
|
||||||
|
public decimal InStock { get; set; }
|
||||||
|
public bool Insufficient { get { return Required > InStock; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Models/Translations.cs
Normal file
12
Models/Translations.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class Translations
|
||||||
|
{
|
||||||
|
public List<string> Africa { get; set; } = new List<string>();
|
||||||
|
public List<string> Asia { get; set; } = new List<string>();
|
||||||
|
public List<string> Europe { get; set; } = new List<string>();
|
||||||
|
public List<string> NorthAmerica { get; set; } = new List<string>();
|
||||||
|
public List<string> SouthAmerica { get; set; } = new List<string>();
|
||||||
|
public List<string> Oceania { get; set; } = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public List<int> ReminderRecordId { get; set; } = new List<int>();
|
public List<int> ReminderRecordId { get; set; } = new List<int>();
|
||||||
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
public string Date { get; set; } = DateTime.Now.ToShortDateString();
|
||||||
public int Mileage { get; set; }
|
public decimal Mileage { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
public bool EnableShopSupplies { get; set; }
|
public bool EnableShopSupplies { get; set; }
|
||||||
public bool EnableExtraFieldColumns { get; set; }
|
public bool EnableExtraFieldColumns { get; set; }
|
||||||
public bool HideSoldVehicles { get; set; }
|
public bool HideSoldVehicles { get; set; }
|
||||||
|
public bool AutomaticDecimalFormat { get; set; }
|
||||||
public string PreferredGasUnit { get; set; } = string.Empty;
|
public string PreferredGasUnit { get; set; } = string.Empty;
|
||||||
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
||||||
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
|
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
|
||||||
@@ -35,7 +36,21 @@
|
|||||||
ImportMode.UpgradeRecord,
|
ImportMode.UpgradeRecord,
|
||||||
ImportMode.TaxRecord,
|
ImportMode.TaxRecord,
|
||||||
ImportMode.ReminderRecord,
|
ImportMode.ReminderRecord,
|
||||||
ImportMode.NoteRecord};
|
ImportMode.NoteRecord
|
||||||
|
};
|
||||||
public ImportMode DefaultTab { get; set; } = ImportMode.Dashboard;
|
public ImportMode DefaultTab { get; set; } = ImportMode.Dashboard;
|
||||||
|
public List<ImportMode> TabOrder { get; set; } = new List<ImportMode>() {
|
||||||
|
ImportMode.Dashboard,
|
||||||
|
ImportMode.PlanRecord,
|
||||||
|
ImportMode.OdometerRecord,
|
||||||
|
ImportMode.ServiceRecord,
|
||||||
|
ImportMode.RepairRecord,
|
||||||
|
ImportMode.UpgradeRecord,
|
||||||
|
ImportMode.GasRecord,
|
||||||
|
ImportMode.SupplyRecord,
|
||||||
|
ImportMode.TaxRecord,
|
||||||
|
ImportMode.NoteRecord,
|
||||||
|
ImportMode.ReminderRecord
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,5 +28,9 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string OdometerDifference { get; set; } = "0";
|
public string OdometerDifference { get; set; } = "0";
|
||||||
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
|
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
|
||||||
|
/// <summary>
|
||||||
|
/// Determines what is displayed in place of the license plate.
|
||||||
|
/// </summary>
|
||||||
|
public string VehicleIdentifier { get; set; } = "LicensePlate";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
public bool OdometerOptional { get; set; } = false;
|
public bool OdometerOptional { get; set; } = false;
|
||||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||||
public List<string> Tags { get; set; } = new List<string>();
|
public List<string> Tags { get; set; } = new List<string>();
|
||||||
|
public string VehicleIdentifier { get; set; } = "LicensePlate";
|
||||||
//Dashboard Metric Attributes
|
//Dashboard Metric Attributes
|
||||||
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
|
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
|
||||||
public int LastReportedMileage { get; set; }
|
public decimal LastReportedMileage { get; set; }
|
||||||
public bool HasReminders { get; set; } = false;
|
public bool HasReminders { get; set; } = false;
|
||||||
public decimal CostPerMile { get; set; }
|
public decimal CostPerMile { get; set; }
|
||||||
public decimal TotalCost { get; set; }
|
public decimal TotalCost { get; set; }
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Installation/Getti
|
|||||||
- [Chart.js](https://github.com/chartjs/Chart.js)
|
- [Chart.js](https://github.com/chartjs/Chart.js)
|
||||||
- [Drawdown](https://github.com/adamvleggett/drawdown)
|
- [Drawdown](https://github.com/adamvleggett/drawdown)
|
||||||
- [MailKit](https://github.com/jstedfast/MailKit)
|
- [MailKit](https://github.com/jstedfast/MailKit)
|
||||||
|
- [Masonry](https://github.com/desandro/masonry)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "LubeLogger API";
|
ViewData["Title"] = "API";
|
||||||
}
|
}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<h6>Parameters</h6>
|
<h6>Parameters</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
No Params
|
No Params
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
VehicleId - Id of Vehicle(optional)
|
VehicleId - Id of Vehicle(optional)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
odometer - Unadjusted odometer
|
odometer - Unadjusted odometer
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
@@ -116,11 +116,12 @@
|
|||||||
initialOdometer - Initial Odometer reading(optional)<br />
|
initialOdometer - Initial Odometer reading(optional)<br />
|
||||||
odometer - Odometer reading<br />
|
odometer - Odometer reading<br />
|
||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +135,7 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
@@ -153,11 +154,12 @@
|
|||||||
description - Description<br/>
|
description - Description<br/>
|
||||||
cost - Cost<br />
|
cost - Cost<br />
|
||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +173,7 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
@@ -190,11 +192,12 @@
|
|||||||
description - Description<br />
|
description - Description<br />
|
||||||
cost - Cost<br />
|
cost - Cost<br />
|
||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -208,7 +211,7 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
@@ -227,11 +230,12 @@
|
|||||||
description - Description<br />
|
description - Description<br />
|
||||||
cost - Cost<br />
|
cost - Cost<br />
|
||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -245,7 +249,7 @@
|
|||||||
vehicleId - Id of Vehicle
|
vehicleId - Id of Vehicle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
@@ -263,11 +267,12 @@
|
|||||||
description - Description<br />
|
description - Description<br />
|
||||||
cost - Cost<br />
|
cost - Cost<br />
|
||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -285,7 +290,7 @@
|
|||||||
useUKMPG(bool) - Use UK Imperial Calculation
|
useUKMPG(bool) - Use UK Imperial Calculation
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
POST
|
POST
|
||||||
</div>
|
</div>
|
||||||
@@ -306,11 +311,12 @@
|
|||||||
isFillToFull(bool) - Filled To Full<br />
|
isFillToFull(bool) - Filled To Full<br />
|
||||||
missedFuelUp(bool) - Missed Fuel Up<br />
|
missedFuelUp(bool) - Missed Fuel Up<br />
|
||||||
notes - notes(optional)<br />
|
notes - notes(optional)<br />
|
||||||
|
tags - tags separated by space(optional)<br />
|
||||||
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -326,7 +332,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -341,7 +347,7 @@
|
|||||||
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
|
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
@@ -355,7 +361,7 @@
|
|||||||
No Params(must be root user)
|
No Params(must be root user)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row api-method">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@using CarCareTracker.Helper
|
@using CarCareTracker.Helper
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Admin";
|
ViewData["Title"] = "Admin Panel";
|
||||||
}
|
}
|
||||||
@inject IConfiguration config;
|
@inject IConfiguration config;
|
||||||
@inject ITranslationHelper translator
|
@inject ITranslationHelper translator
|
||||||
@@ -25,44 +25,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5 col-12">
|
<div class="col-12">
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
|
|
||||||
<hr />
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-2 d-flex align-items-center">
|
||||||
<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">@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">@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>
|
|
||||||
@foreach (Token token in Model.Tokens)
|
|
||||||
{
|
|
||||||
<tr class="d-flex">
|
|
||||||
<td class="col-4" style="cursor:pointer;" onclick="copyToClipboard(this)">@token.Body</td>
|
|
||||||
<td class="col-6 text-truncate">@token.EmailAddress</td>
|
|
||||||
<td class="col-2">
|
|
||||||
<button type="button" class="btn btn-danger" onclick="deleteToken(@token.Id, this)"><i class="bi bi-trash"></i></button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-7">
|
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Users")</span>
|
<span class="lead">@translator.Translate(userLanguage, "Users")</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 d-flex align-items-center justify-content-end">
|
||||||
|
<button onclick="showTokenModal()" class="btn btn-primary btn-md mt-1 mb-1">
|
||||||
|
<i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Manage Tokens")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="sticky-top">
|
<thead class="sticky-top">
|
||||||
@@ -73,58 +46,119 @@
|
|||||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
|
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="userTable">
|
||||||
@foreach (UserData userData in Model.Users)
|
@await Html.PartialAsync("_Users", Model.Users)
|
||||||
{
|
|
||||||
<tr class="d-flex" style="cursor:pointer;">
|
|
||||||
<td class="col-4">@userData.UserName</td>
|
|
||||||
<td class="col-4">@userData.EmailAddress</td>
|
|
||||||
<td class="col-2"><input class="form-check-input" type="checkbox" value="" onchange="updateUserAdmin(@userData.Id, this)" @(userData.IsAdmin ? "checked" : "")/></td>
|
|
||||||
<td class="col-2"><button type="button" class="btn btn-danger" onclick="deleteUser(@userData.Id, this)"><i class="bi bi-trash"></i></button></td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="tokenModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center ms-auto">
|
||||||
|
<div class="btn-group">
|
||||||
|
<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")
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-primary btn-md mt-1 mb-1" @(emailServerIsSetup ? "" : "disabled") onclick="toggleAutoNotify(event)">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="enableAutoNotify" @(emailServerIsSetup ? "checked" : "disabled")>
|
||||||
|
<label class="form-check-label" for="enableAutoNotify">@translator.Translate(userLanguage, "Notify")</label>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-close btn-md mt-1 mb-1 ms-2" onclick="hideTokenModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="sticky-top">
|
||||||
|
<tr class="d-flex">
|
||||||
|
<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 id="tokenTable">
|
||||||
|
@await Html.PartialAsync("_Tokens", Model.Tokens)
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function updateUserAdmin(userId, sender){
|
function showTokenModal() {
|
||||||
|
$("#tokenModal").modal('show');
|
||||||
|
}
|
||||||
|
function hideTokenModal() {
|
||||||
|
$("#tokenModal").modal('hide');
|
||||||
|
}
|
||||||
|
function reloadTokenTable() {
|
||||||
|
$.get('/Admin/GetTokenPartialView', function (data) {
|
||||||
|
$("#tokenTable").html(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function reloadUserTable() {
|
||||||
|
$.get('/Admin/GetUserPartialView', function (data) {
|
||||||
|
$("#userTable").html(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function updateUserAdmin(userId, sender) {
|
||||||
var isChecked = $(sender).is(":checked");
|
var isChecked = $(sender).is(":checked");
|
||||||
$.post('/Admin/UpdateUserAdminStatus', { userId: userId, isAdmin: isChecked }, function (data) {
|
$.post('/Admin/UpdateUserAdminStatus', { userId: userId, isAdmin: isChecked }, function (data) {
|
||||||
if (data){
|
if (data) {
|
||||||
reloadPage();
|
reloadUserTable();
|
||||||
} else {
|
} else {
|
||||||
errorToast(genericErrorMessage());
|
errorToast(genericErrorMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function reloadPage() {
|
function toggleAutoNotify(e) {
|
||||||
window.location.reload();
|
if (!$("#enableAutoNotify").attr("disabled")) {
|
||||||
|
if ($(e.target).hasClass('btn')) {
|
||||||
|
$("#enableAutoNotify").trigger('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function deleteToken(tokenId) {
|
function deleteToken(tokenId) {
|
||||||
$.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) {
|
$.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
reloadPage();
|
reloadTokenTable();
|
||||||
} else {
|
} else {
|
||||||
errorToast(genericErrorMessage());
|
errorToast(genericErrorMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function deleteUser(userId) {
|
function deleteUser(userId) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Confirm Deletion?",
|
||||||
|
text: "Deleted Users cannot be restored.",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: "Delete",
|
||||||
|
confirmButtonColor: "#dc3545"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
$.post(`/Admin/DeleteUser?userId=${userId}`, function (data) {
|
$.post(`/Admin/DeleteUser?userId=${userId}`, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
reloadPage();
|
reloadUserTable();
|
||||||
} else {
|
} else {
|
||||||
errorToast(genericErrorMessage());
|
errorToast(genericErrorMessage());
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function generateNewToken() {
|
function generateNewToken() {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Generate Token',
|
title: 'Generate Token',
|
||||||
html: `
|
html: `
|
||||||
<input type="text" id="inputEmail" class="swal2-input" placeholder="Email Address">
|
<input type="text" id="inputEmail" class="swal2-input" placeholder="Email Address" onkeydown="handleSwalEnter(event)">
|
||||||
`,
|
`,
|
||||||
confirmButtonText: 'Generate',
|
confirmButtonText: 'Generate',
|
||||||
focusConfirm: false,
|
focusConfirm: false,
|
||||||
@@ -140,7 +174,7 @@
|
|||||||
var autoNotify = $("#enableAutoNotify").is(":checked");
|
var autoNotify = $("#enableAutoNotify").is(":checked");
|
||||||
$.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) {
|
$.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
reloadPage();
|
reloadTokenTable();
|
||||||
} else {
|
} else {
|
||||||
errorToast(data.message)
|
errorToast(data.message)
|
||||||
}
|
}
|
||||||
|
|||||||
12
Views/Admin/_Tokens.cshtml
Normal file
12
Views/Admin/_Tokens.cshtml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@model List<Token>
|
||||||
|
@foreach (Token token in Model)
|
||||||
|
{
|
||||||
|
<tr class="d-flex">
|
||||||
|
<td class="col-4" style="cursor:pointer;" onclick="copyToClipboard(this)">@token.Body</td>
|
||||||
|
<td class="col-6 text-truncate">@StaticHelper.TruncateStrings(token.EmailAddress)</td>
|
||||||
|
<td class="col-2">
|
||||||
|
<button type="button" class="btn btn-danger" onclick="deleteToken(@token.Id, this)"><i class="bi bi-trash"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
11
Views/Admin/_Users.cshtml
Normal file
11
Views/Admin/_Users.cshtml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@model List<UserData>
|
||||||
|
@foreach (UserData userData in Model)
|
||||||
|
{
|
||||||
|
<tr class="d-flex" style="cursor:pointer;">
|
||||||
|
<td class="col-4 d-flex align-items-center">@StaticHelper.TruncateStrings(userData.UserName)</td>
|
||||||
|
<td class="col-4 d-flex align-items-center">@StaticHelper.TruncateStrings(userData.EmailAddress)</td>
|
||||||
|
<td class="col-2 d-flex align-items-center"><input class="form-check-input" type="checkbox" value="" onchange="updateUserAdmin(@userData.Id, this)" @(userData.IsAdmin ? "checked" : "") /></td>
|
||||||
|
<td class="col-2 d-flex align-items-center"><button type="button" class="btn btn-danger" onclick="deleteUser(@userData.Id, this)"><i class="bi bi-trash"></i></button></td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
@@ -5,11 +5,10 @@
|
|||||||
var userConfig = config.GetUserConfig(User);
|
var userConfig = config.GetUserConfig(User);
|
||||||
var enableAuth = userConfig.EnableAuth;
|
var enableAuth = userConfig.EnableAuth;
|
||||||
var userLanguage = userConfig.UserLanguage;
|
var userLanguage = userConfig.UserLanguage;
|
||||||
var logoUrl = config.GetLogoUrl();
|
|
||||||
}
|
}
|
||||||
@model string
|
@model string
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "LubeLogger";
|
ViewData["Title"] = "Garage";
|
||||||
}
|
}
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="~/js/garage.js?v=@StaticHelper.VersionNumber"></script>
|
<script src="~/js/garage.js?v=@StaticHelper.VersionNumber"></script>
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="d-flex lubelogger-navbar">
|
<div class="d-flex lubelogger-navbar">
|
||||||
<img src="@logoUrl" />
|
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||||
<div class="lubelogger-navbar-button">
|
<div class="lubelogger-navbar-button">
|
||||||
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,24 +70,24 @@
|
|||||||
<hr />
|
<hr />
|
||||||
<ul class="nav nav-tabs lubelogger-tab" id="homeTab" role="tablist">
|
<ul class="nav nav-tabs lubelogger-tab" id="homeTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<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>
|
<button class="nav-link resizable-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"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Garage")</span></button>
|
||||||
</li>
|
</li>
|
||||||
@if (config.GetServerEnableShopSupplies())
|
@if (config.GetServerEnableShopSupplies())
|
||||||
{
|
{
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" 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>
|
<button class="nav-link resizable-nav-link" 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"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Supplies")</span></button>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-calendar-week me-2"></i>@translator.Translate(userLanguage, "Calendar")</button>
|
<button class="nav-link resizable-nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-calendar-week"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Calendar")</span></button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item ms-auto" role="presentation">
|
<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>@translator.Translate(userLanguage,"Settings")</button>
|
<button class="nav-link resizable-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"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Settings")</span></button>
|
||||||
</li>
|
</li>
|
||||||
@if (User.IsInRole("CookieAuth"))
|
@if (User.IsInRole("CookieAuth"))
|
||||||
{
|
{
|
||||||
<li class="nav-item dropdown" role="presentation">
|
<li class="nav-item dropdown" role="presentation">
|
||||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person me-2"></i>@User.Identity.Name</a>
|
<a class="nav-link resizable-nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person"></i><span class="ms-2 d-sm-none d-md-inline">@User.Identity.Name</span></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
@if (User.IsInRole(nameof(UserData.IsAdmin)))
|
||||||
{
|
{
|
||||||
|
|||||||
148
Views/Home/Kiosk.cshtml
Normal file
148
Views/Home/Kiosk.cshtml
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Kiosk";
|
||||||
|
}
|
||||||
|
@model KioskViewModel
|
||||||
|
@section Scripts {
|
||||||
|
<script src="~/lib/masonry/masonry.min.js"></script>
|
||||||
|
}
|
||||||
|
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
|
||||||
|
<div class="progress-bar" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
<div id="kioskContainer" class="container-fluid">
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let refreshTimer;
|
||||||
|
let exceptionList = [];
|
||||||
|
let subtractAmount = 0;
|
||||||
|
let kioskMode = '@Model.KioskMode';
|
||||||
|
let currentKioskMode = 'Plan';
|
||||||
|
let kioskWakeLock;
|
||||||
|
|
||||||
|
@foreach(int exception in Model.Exclusions)
|
||||||
|
{
|
||||||
|
@:exceptionList.push(@exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initKiosk() {
|
||||||
|
$("body > div").removeClass("container");
|
||||||
|
$("body > div").css('height', '100vh');
|
||||||
|
subtractAmount = parseInt($("#kioskContainer").width() * 0.0016); //remove 0.0016% of width every 100 ms which will approximate to one minute.
|
||||||
|
if (subtractAmount < 2) {
|
||||||
|
subtractAmount = 2;
|
||||||
|
}
|
||||||
|
retrieveKioskContent();
|
||||||
|
//acquire wakeLock;
|
||||||
|
try {
|
||||||
|
navigator.wakeLock.request('screen').then((wl) => {
|
||||||
|
kioskWakeLock = wl;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
errorToast('Location Services not Enabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function setAccessToken(accessToken){
|
||||||
|
//use this function to never worry about user session expiring.
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Basic ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("Access Token for Kiosk Mode Configured!");
|
||||||
|
}
|
||||||
|
function retrieveKioskContent(){
|
||||||
|
clearInterval(refreshTimer);
|
||||||
|
if (kioskMode != 'Cycle'){
|
||||||
|
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: kioskMode }, function (data) {
|
||||||
|
$("#kioskContainer").html(data);
|
||||||
|
$(".kiosk-content").masonry();
|
||||||
|
if ($(".no-data-message").length == 0) {
|
||||||
|
$(".progress-bar").width($("#kioskContainer").width());
|
||||||
|
setTimeout(function () { startTimer() }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//cycle mode
|
||||||
|
switch (currentKioskMode) {
|
||||||
|
case "Vehicle":
|
||||||
|
currentKioskMode = "Reminder";
|
||||||
|
break;
|
||||||
|
case "Reminder":
|
||||||
|
currentKioskMode = "Plan";
|
||||||
|
break;
|
||||||
|
case "Plan":
|
||||||
|
currentKioskMode = "Vehicle";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: currentKioskMode }, function (data) {
|
||||||
|
$("#kioskContainer").html(data);
|
||||||
|
$(".kiosk-content").masonry();
|
||||||
|
if ($(".no-data-message").length > 0) {
|
||||||
|
//if no data on vehicle page
|
||||||
|
if (currentKioskMode == "Vehicle") {
|
||||||
|
return; //exit
|
||||||
|
} else {
|
||||||
|
retrieveKioskContent(); //skip until we hit a page with content.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$(".progress-bar").width($("#kioskContainer").width());
|
||||||
|
setTimeout(function () { startTimer() }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
function startTimer() {
|
||||||
|
refreshTimer = setInterval(function () {
|
||||||
|
var currentWidth = $(".progress-bar").width();
|
||||||
|
if (currentWidth > 0) {
|
||||||
|
$(".progress-bar").width(currentWidth - subtractAmount);
|
||||||
|
} else {
|
||||||
|
retrieveKioskContent();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
function addVehicleToExceptionList(vehicleId) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Remove Vehicle from Dashboard?",
|
||||||
|
text: "Removed vehicles can be restored by refreshing the page",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: "Remove",
|
||||||
|
confirmButtonColor: "#dc3545"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
exceptionList.push(vehicleId);
|
||||||
|
if (kioskMode == 'Cycle') {
|
||||||
|
//remove the vehicle programmatically.
|
||||||
|
$(`[data-vehicleId=${vehicleId}]`).remove();
|
||||||
|
} else {
|
||||||
|
//force a refresh
|
||||||
|
retrieveKioskContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function toggleReminderNote(sender){
|
||||||
|
var reminderNote = $(sender).find('.reminder-note');
|
||||||
|
if (reminderNote.text().trim() != ''){
|
||||||
|
if (reminderNote.hasClass('d-none')) {
|
||||||
|
reminderNote.removeClass('d-none');
|
||||||
|
} else {
|
||||||
|
reminderNote.addClass('d-none');
|
||||||
|
}
|
||||||
|
$(".kiosk-content").masonry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function togglePlanDetails(sender) {
|
||||||
|
toggleReminderNote(sender);
|
||||||
|
var planSupplies = $(sender).find('.plan-supplies');
|
||||||
|
if (planSupplies.find('.plan-supply').length > 0) {
|
||||||
|
if (planSupplies.hasClass('d-none')) {
|
||||||
|
planSupplies.removeClass('d-none');
|
||||||
|
} else {
|
||||||
|
planSupplies.addClass('d-none');
|
||||||
|
}
|
||||||
|
$(".kiosk-content").masonry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initKiosk();
|
||||||
|
</script>
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
@{
|
|
||||||
ViewData["Title"] = "Privacy Policy";
|
|
||||||
}
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
|
|
||||||
<p>Use this page to detail your site's privacy policy.</p>
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<h5 class="modal-title" id="updateAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
|
<h5 class="modal-title" id="updateAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
|
||||||
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
|
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body" onkeydown="handleEnter(this)">
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUsername">@translator.Translate(userLanguage, "Username")</label>
|
<label for="inputUsername">@translator.Translate(userLanguage, "Username")</label>
|
||||||
@@ -18,7 +18,12 @@
|
|||||||
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
|
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
|
||||||
<input type="text" id="inputEmail" class="form-control" placeholder="@translator.Translate(userLanguage, "Email Address")" value="@Model.EmailAddress">
|
<input type="text" id="inputEmail" class="form-control" placeholder="@translator.Translate(userLanguage, "Email Address")" value="@Model.EmailAddress">
|
||||||
<label for="inputPassword">@translator.Translate(userLanguage, "New Password")</label>
|
<label for="inputPassword">@translator.Translate(userLanguage, "New Password")</label>
|
||||||
|
<div class="input-group">
|
||||||
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "New Password")" value="">
|
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "New Password")" value="">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||||
<input type="text" id="inputToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Token")" value="">
|
<input type="text" id="inputToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Token")" value="">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Field Name',
|
title: 'Field Name',
|
||||||
html: `
|
html: `
|
||||||
<input type="text" id="inputExtraFieldName" class="swal2-input" placeholder="Field Name">
|
<input type="text" id="inputExtraFieldName" class="swal2-input" placeholder="Field Name" onkeydown="handleSwalEnter(event)">
|
||||||
`,
|
`,
|
||||||
confirmButtonText: 'Add',
|
confirmButtonText: 'Add',
|
||||||
focusConfirm: false,
|
focusConfirm: false,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
{
|
{
|
||||||
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
|
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
|
||||||
{
|
{
|
||||||
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" 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="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" 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)">
|
<div class="card" onclick="viewVehicle(@vehicle.Id)">
|
||||||
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
|
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
|
||||||
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
|
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="ms-2"><i class="bi bi-speedometer me-2"></i>@vehicle.LastReportedMileage.ToString("N0")</span>
|
<span class="ms-2"><i class="bi bi-speedometer me-2"></i>@vehicle.LastReportedMileage.ToString("N1")</span>
|
||||||
</div>
|
</div>
|
||||||
@if (vehicle.HasReminders)
|
@if (vehicle.HasReminders)
|
||||||
{
|
{
|
||||||
@@ -76,13 +76,13 @@
|
|||||||
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{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.Make}")</h5>
|
||||||
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5>
|
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5>
|
||||||
<p class="card-text text-truncate">@vehicle.LicensePlate</p>
|
<p class="card-text text-truncate">@StaticHelper.GetVehicleIdentifier(vehicle)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 garage-item-add">
|
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 garage-item-add">
|
||||||
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
|
||||||
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
|
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
126
Views/Home/_Kiosk.cshtml
Normal file
126
Views/Home/_Kiosk.cshtml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@model List<VehicleInfo>
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
|
||||||
|
@foreach (VehicleInfo vehicle in Model)
|
||||||
|
{
|
||||||
|
<div class="col" data-vehicleId="@vehicle.VehicleData.Id">
|
||||||
|
<div class="card" onclick="addVehicleToExceptionList(@vehicle.VehicleData.Id)">
|
||||||
|
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||||
|
<h5 class="card-title">@($"{vehicle.VehicleData.Year} {vehicle.VehicleData.Make} {vehicle.VehicleData.Model} ({StaticHelper.GetVehicleIdentifier(vehicle.VehicleData)})")</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.ServiceRecordCount</p>
|
||||||
|
<p class="lead text-truncate">@translator.Translate(userLanguage, "Service")</p>
|
||||||
|
<p class="lead text-truncate">@vehicle.ServiceRecordCost.ToString("C0")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.RepairRecordCount</p>
|
||||||
|
<p class="lead text-truncate">@translator.Translate(userLanguage, "Repairs")</p>
|
||||||
|
<p class="lead text-truncate">@vehicle.RepairRecordCost.ToString("C0")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.UpgradeRecordCount</p>
|
||||||
|
<p class="lead text-truncate">@translator.Translate(userLanguage, "Upgrades")</p>
|
||||||
|
<p class="lead text-truncate">@vehicle.UpgradeRecordCost.ToString("C0")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.GasRecordCount</p>
|
||||||
|
<p class="lead text-truncate">@translator.Translate(userLanguage, "Fuel")</p>
|
||||||
|
<p class="lead text-truncate">@vehicle.GasRecordCost.ToString("C0")</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (vehicle.PastDueReminderCount + vehicle.VeryUrgentReminderCount + vehicle.UrgentReminderCount + vehicle.NotUrgentReminderCount > 0)
|
||||||
|
{
|
||||||
|
<hr style="margin:0px;" />
|
||||||
|
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||||
|
<h5 class="card-title">@translator.Translate(userLanguage, "Reminders")</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.PastDueReminderCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Past Due")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.VeryUrgentReminderCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Very Urgent")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.UrgentReminderCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Urgent")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.NotUrgentReminderCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Not Urgent")</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (vehicle.NextReminder != null)
|
||||||
|
{
|
||||||
|
<hr style="margin:0px;" />
|
||||||
|
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||||
|
<h5 class="card-title">@translator.Translate(userLanguage, "Upcoming Reminder")</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="display-7">@vehicle.NextReminder.Description</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(vehicle.NextReminder.Urgency))</p>
|
||||||
|
<div class="row">
|
||||||
|
@if (vehicle.NextReminder.Metric == "Date" || vehicle.NextReminder.Metric == "Both")
|
||||||
|
{
|
||||||
|
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@vehicle.NextReminder.DueDate</div>
|
||||||
|
}
|
||||||
|
@if (vehicle.NextReminder.Metric == "Odometer" || vehicle.NextReminder.Metric == "Both")
|
||||||
|
{
|
||||||
|
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@vehicle.NextReminder.DueOdometer</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (vehicle.PlanRecordBackLogCount + vehicle.PlanRecordInProgressCount + vehicle.PlanRecordTestingCount + vehicle.PlanRecordBackLogCount > 0)
|
||||||
|
{
|
||||||
|
<hr style="margin:0px;" />
|
||||||
|
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||||
|
<h5 class="card-title">@translator.Translate(userLanguage, "Plans")</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.PlanRecordBackLogCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Planned")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.PlanRecordInProgressCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Doing")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.PlanRecordTestingCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Testing")</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<p class="display-7">@vehicle.PlanRecordDoneCount</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, "Done")</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row no-data-message">
|
||||||
|
<div class="col">
|
||||||
|
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
86
Views/Home/_KioskPlan.cshtml
Normal file
86
Views/Home/_KioskPlan.cshtml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@model List<PlanRecord>
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
|
||||||
|
@foreach (PlanRecord plan in Model)
|
||||||
|
{
|
||||||
|
<div class="col" onclick="togglePlanDetails(this)">
|
||||||
|
<div class="card @StaticHelper.GetPlanRecordColor(plan.Priority)">
|
||||||
|
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||||
|
<h5 class="card-title">@plan.Description</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@plan.Notes</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetPlanRecordProgress(plan.Progress))</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
@if (plan.ImportMode == ImportMode.ServiceRecord)
|
||||||
|
{
|
||||||
|
<span class="lead">@translator.Translate(userLanguage, "Service")</span>
|
||||||
|
}
|
||||||
|
else if (plan.ImportMode == ImportMode.UpgradeRecord)
|
||||||
|
{
|
||||||
|
<span class="lead">@translator.Translate(userLanguage, "Repairs")</span>
|
||||||
|
}
|
||||||
|
else if (plan.ImportMode == ImportMode.RepairRecord)
|
||||||
|
{
|
||||||
|
<span class="lead">@translator.Translate(userLanguage, "Upgrades")</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (plan.RequisitionHistory.Any())
|
||||||
|
{
|
||||||
|
<ul class="list-group list-group-flush plan-supplies d-none">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
@translator.Translate(userLanguage, "Part Number")
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
@translator.Translate(userLanguage, "Description")
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
@translator.Translate(userLanguage, "Quantity")
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@foreach (SupplyUsageHistory supply in plan.RequisitionHistory)
|
||||||
|
{
|
||||||
|
<li class="list-group-item plan-supply">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
@supply.PartNumber
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
@supply.Description
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
@supply.Quantity
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
<div class="row no-data-message">
|
||||||
|
<div class="col">
|
||||||
|
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
47
Views/Home/_KioskReminder.cshtml
Normal file
47
Views/Home/_KioskReminder.cshtml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@model List<ReminderRecordViewModel>
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
|
||||||
|
@foreach (ReminderRecordViewModel reminder in Model)
|
||||||
|
{
|
||||||
|
<div class="col" onclick="toggleReminderNote(this)">
|
||||||
|
<div class="card @StaticHelper.GetReminderUrgencyColor(reminder.Urgency)">
|
||||||
|
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
|
||||||
|
<h5 class="card-title">@reminder.Description</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@reminder.Notes</p>
|
||||||
|
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))</p>
|
||||||
|
<div class="row">
|
||||||
|
@if (reminder.Metric == ReminderMetric.Date || reminder.Metric == ReminderMetric.Both)
|
||||||
|
{
|
||||||
|
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@reminder.Date.ToShortDateString()</div>
|
||||||
|
}
|
||||||
|
@if (reminder.Metric == ReminderMetric.Odometer || reminder.Metric == ReminderMetric.Both)
|
||||||
|
{
|
||||||
|
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@reminder.Mileage</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row no-data-message">
|
||||||
|
<div class="col">
|
||||||
|
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -10,13 +10,18 @@
|
|||||||
<h5 class="modal-title" id="updateRootAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
|
<h5 class="modal-title" id="updateRootAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
|
||||||
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
|
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body" onkeydown="handleEnter(this)">
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUsername">@translator.Translate(userLanguage, "Username")</label>
|
<label for="inputUsername">@translator.Translate(userLanguage, "Username")</label>
|
||||||
<input type="text" id="inputUsername" class="form-control" placeholder="@translator.Translate(userLanguage, "Account Username")" value="@Model.UserName">
|
<input type="text" id="inputUsername" class="form-control" placeholder="@translator.Translate(userLanguage, "Account Username")" value="@Model.UserName">
|
||||||
<label for="inputPassword">@translator.Translate(userLanguage, "Password")</label>
|
<label for="inputPassword">@translator.Translate(userLanguage, "Password")</label>
|
||||||
|
<div class="input-group">
|
||||||
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "Password")" value="">
|
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "Password")" value="">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,10 +37,14 @@
|
|||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UserConfig.UseDescending">
|
<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>
|
<label class="form-check-label" for="useDescending">@translator.Translate(userLanguage, "Sort lists in Descending Order(Newest to Oldest)")</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch form-check-inline">
|
||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.UserConfig.HideZero">
|
<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>
|
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 Costs with ---")</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="automaticDecimalFormat" checked="@Model.UserConfig.AutomaticDecimalFormat">
|
||||||
|
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
|
||||||
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
|
<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>
|
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
|
||||||
@@ -71,13 +75,13 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
{
|
{
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch form-check-inline">
|
||||||
<input class="form-check-input" onChange="enableAuthCheckChanged()" type="checkbox" role="switch" id="enableAuth" checked="@Model.UserConfig.EnableAuth">
|
<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>
|
<label class="form-check-label" for="enableAuth">@translator.Translate(userLanguage, "Enable Authentication")</label>
|
||||||
</div>
|
</div>
|
||||||
@if (Model.UserConfig.EnableAuth)
|
@if (Model.UserConfig.EnableAuth)
|
||||||
{
|
{
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch form-check-inline">
|
||||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="disableRegistration" checked="@Model.UserConfig.DisableRegistration">
|
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="disableRegistration" checked="@Model.UserConfig.DisableRegistration">
|
||||||
<label class="form-check-label" for="disableRegistration">@translator.Translate(userLanguage, "Disable Registration")</label>
|
<label class="form-check-label" for="disableRegistration">@translator.Translate(userLanguage, "Disable Registration")</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,8 +95,15 @@
|
|||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<div class="row" id="visibleTabs">
|
<div class="row" id="visibleTabs">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-11">
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Visible Tabs")</span>
|
<span class="lead">@translator.Translate(userLanguage, "Visible Tabs")</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<button onclick="showTabReorderModal()" class="btn text-secondary btn-sm"><i class="bi bi-arrow-down-up"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
@@ -165,12 +176,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Language")</span>
|
<span class="lead">@translator.Translate(userLanguage, "Language")</span>
|
||||||
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
|
{
|
||||||
|
<div class="input-group">
|
||||||
<select class="form-select" onchange="updateSettings()" id="defaultLanguage">
|
<select class="form-select" onchange="updateSettings()" id="defaultLanguage">
|
||||||
@foreach (string uiLanguage in Model.UILanguages)
|
@foreach (string uiLanguage in Model.UILanguages)
|
||||||
{
|
{
|
||||||
<!option @(Model.UserConfig.UserLanguage == uiLanguage ? "selected" : "")>@uiLanguage</!option>
|
<!option value="@uiLanguage" @(Model.UserConfig.UserLanguage == uiLanguage ? "selected" : "")>@StaticHelper.GetTranslationName(uiLanguage)</!option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
|
<div class="input-group-text">
|
||||||
|
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="showTranslationEditor()"><i class="bi bi-pencil"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
<select class="form-select" onchange="updateSettings()" id="defaultLanguage">
|
||||||
|
@foreach (string uiLanguage in Model.UILanguages)
|
||||||
|
{
|
||||||
|
<!option value="@uiLanguage" @(Model.UserConfig.UserLanguage == uiLanguage ? "selected" : "")>@StaticHelper.GetTranslationName(uiLanguage)</!option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
||||||
@@ -180,11 +207,11 @@
|
|||||||
<span class="lead">@translator.Translate(userLanguage, "Backups")</span>
|
<span class="lead">@translator.Translate(userLanguage, "Backups")</span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 d-grid">
|
<div class="col-6 d-grid">
|
||||||
<button onclick="makeBackup()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Create")</button>
|
<button onclick="makeBackup()" class="btn btn-primary btn-md text-truncate">@translator.Translate(userLanguage, "Create")</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 d-grid">
|
<div class="col-6 d-grid">
|
||||||
<input onChange="restoreBackup(this)" type="file" accept=".zip" class="d-none" id="inputBackup">
|
<input onChange="restoreBackup(this)" type="file" accept=".zip" class="d-none" id="inputBackup">
|
||||||
<button onclick="openRestoreBackup()" class="btn btn-secondary btn-md">@translator.Translate(userLanguage, "Restore")</button>
|
<button onclick="openRestoreBackup()" class="btn btn-secondary btn-md text-truncate">@translator.Translate(userLanguage, "Restore")</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,32 +220,41 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 d-grid">
|
<div class="col-6 d-grid">
|
||||||
<input onChange="uploadLanguage(this)" type="file" accept=".json" class="d-none" id="inputLanguage">
|
<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 class="btn-group">
|
||||||
|
<button onclick="openUploadLanguage()" class="btn btn-primary btn-md text-truncate"><i class="bi bi-upload"></i><span class="ms-2 d-sm-inline d-md-none d-xl-inline">@translator.Translate(userLanguage, "Upload")</span></button>
|
||||||
|
<button type="button" class="btn btn-md btn-primary btn-md 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="showTranslationDownloader()">@translator.Translate(userLanguage, "Get Translations")</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 d-grid">
|
<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>
|
<button onclick="deleteLanguage()" @(Model.UserConfig.UserLanguage == "en_US" ? "disabled" : "") class="btn btn-danger btn-md text-truncate"><i class="bi bi-trash"></i><span class="ms-2 d-sm-inline d-md-none d-xl-inline">@translator.Translate(userLanguage, "Delete")</span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Server-wide Settings")</span>
|
<span class="lead text-wrap">@translator.Translate(userLanguage, "Server-wide Settings")</span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 d-grid">
|
<div class="col-6 d-grid">
|
||||||
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Extra Fields")</button>
|
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md text-truncate">@translator.Translate(userLanguage, "Extra Fields")</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 d-grid">
|
<div class="col-6 d-grid">
|
||||||
<button onclick="showReminderUrgencyThresholdModal()" class="btn btn-primary btn-md">@translator.Translate(userLanguage, "Reminders")</button>
|
<button onclick="showReminderUrgencyThresholdModal()" class="btn btn-primary btn-md text-truncate">@translator.Translate(userLanguage, "Reminders")</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<span class="lead">@translator.Translate(userLanguage, "Default Reminder Email")</span>
|
<span class="lead text-wrap">@translator.Translate(userLanguage, "Default Reminder Email")</span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-grid">
|
<div class="col-12 d-grid">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="inputDefaultEmail" class="form-control" placeholder="@translator.Translate(userLanguage,"Default Email for Reminder")" value="@Model.UserConfig.DefaultReminderEmail">
|
<input id="inputDefaultEmail" class="form-control" placeholder="@translator.Translate(userLanguage,"Default Email for Reminder")" value="@Model.UserConfig.DefaultReminderEmail" onkeydown="handleDefaultReminderInputKeyDown()">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="updateSettings()"><i class="bi bi-floppy"></i></button>
|
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="updateSettings()"><i class="bi bi-floppy"></i></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,16 +312,69 @@
|
|||||||
<li class="list-group-item">Chart.js</li>
|
<li class="list-group-item">Chart.js</li>
|
||||||
<li class="list-group-item">Drawdown</li>
|
<li class="list-group-item">Drawdown</li>
|
||||||
<li class="list-group-item">MailKit</li>
|
<li class="list-group-item">MailKit</li>
|
||||||
|
<li class="list-group-item">Masonry</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@await Html.PartialAsync("_Sponsors", Model.Sponsors)
|
<div class="row" id="sponsorsContainer"></div>
|
||||||
<div class="modal fade" data-bs-focus="false" id="extraFieldModal" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="modal fade" data-bs-focus="false" id="extraFieldModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content" id="extraFieldModalContent">
|
<div class="modal-content" id="extraFieldModalContent">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="translationEditorModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content" id="translationEditorModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="translationDownloadModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content" id="translationDownloadModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="customWidgetModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content" id="customWidgetModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="tabReorderModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content" id="tabReorderModalContent">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="translationEditorModalLabel">@translator.Translate(userLanguage, "Reorder Tabs")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideTabReorderModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<ul class="list-group lubelog-tab-groups">
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.Dashboard)" draggable="true" data-tab="@ImportMode.Dashboard">@translator.Translate(userLanguage, "Dashboard")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.PlanRecord)" draggable="true" data-tab="@ImportMode.PlanRecord">@translator.Translate(userLanguage, "Planner")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.OdometerRecord)" draggable="true" data-tab="@ImportMode.OdometerRecord">@translator.Translate(userLanguage, "Odometer")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.ServiceRecord)" draggable="true" data-tab="@ImportMode.ServiceRecord">@translator.Translate(userLanguage, "Service Records")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.RepairRecord)" draggable="true" data-tab="@ImportMode.RepairRecord">@translator.Translate(userLanguage, "Repairs")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.UpgradeRecord)" draggable="true" data-tab="@ImportMode.UpgradeRecord">@translator.Translate(userLanguage, "Upgrades")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.GasRecord)" draggable="true" data-tab="@ImportMode.GasRecord">@translator.Translate(userLanguage, "Fuel")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.SupplyRecord)" draggable="true" data-tab="@ImportMode.SupplyRecord">@translator.Translate(userLanguage, "Supplies")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.TaxRecord)" draggable="true" data-tab="@ImportMode.TaxRecord">@translator.Translate(userLanguage, "Taxes")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)" draggable="true" data-tab="@ImportMode.NoteRecord">@translator.Translate(userLanguage, "Notes")</li>
|
||||||
|
<li class="list-group-item" style="order: @Model.UserConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)" draggable="true" data-tab="@ImportMode.ReminderRecord">@translator.Translate(userLanguage, "Reminder")</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-danger me-auto" onclick="resetTabOrder()">@translator.Translate(userLanguage, "Reset Tab Order")</button>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideTabReorderModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="updateSettings()">@translator.Translate(userLanguage, "Save Tab Order")</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function showReminderUrgencyThresholdModal(){
|
function showReminderUrgencyThresholdModal(){
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
@@ -342,7 +431,7 @@
|
|||||||
title: 'Setup Credentials',
|
title: 'Setup Credentials',
|
||||||
html: `
|
html: `
|
||||||
<input type="text" id="authUsername" class="swal2-input" placeholder="Username">
|
<input type="text" id="authUsername" class="swal2-input" placeholder="Username">
|
||||||
<input type="password" id="authPassword" class="swal2-input" placeholder="Password">
|
<input type="password" id="authPassword" class="swal2-input" placeholder="Password" onkeydown="handleSwalEnter(event)">
|
||||||
`,
|
`,
|
||||||
confirmButtonText: 'Setup',
|
confirmButtonText: 'Setup',
|
||||||
focusConfirm: false,
|
focusConfirm: false,
|
||||||
@@ -375,4 +464,5 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadSponsors();
|
||||||
</script>
|
</script>
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
var enableAuth = userConfig.EnableAuth;
|
var enableAuth = userConfig.EnableAuth;
|
||||||
var userLanguage = userConfig.UserLanguage;
|
var userLanguage = userConfig.UserLanguage;
|
||||||
}
|
}
|
||||||
<div class="row">
|
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<h6 class="display-6 mt-2">@translator.Translate(userLanguage, "Sponsors")</h6>
|
<h6 class="display-6 mt-2">@translator.Translate(userLanguage, "Sponsors")</h6>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,5 +68,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|||||||
48
Views/Home/_TranslationEditor.cshtml
Normal file
48
Views/Home/_TranslationEditor.cshtml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@inject IConfiguration serverConfig;
|
||||||
|
@model Dictionary<string, string>
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
bool showDelete = bool.Parse(serverConfig["LUBELOGGER_TRANSLATOR"] ?? "false");
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="translationEditorModalLabel">@translator.Translate(userLanguage, "Translation Editor")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideTranslationEditor()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" onkeydown="handleEnter(this)">
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="form-group" style="max-height:50vh; overflow-x:hidden; overflow-y:scroll;">
|
||||||
|
@foreach(var translation in Model)
|
||||||
|
{
|
||||||
|
<div class="row translation-keyvalue mb-2 align-items-center">
|
||||||
|
@if (showDelete)
|
||||||
|
{
|
||||||
|
<div class="col-md-1 col-1 translation-delete"><button onclick="deleteTranslationKey(this)" class="btn text-danger btn-sm"><i class="bi bi-x-lg"></i></button></div>
|
||||||
|
<div class="col-md-5 col-11 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
<div class="col-md-6 col-12 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||||
|
}
|
||||||
|
<div class="col-md-6 col-12 translation-value">
|
||||||
|
<textarea style="height:100%;width:98%;" class="form-control" placeholder="@translation.Value">@translation.Value</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideTranslationEditor()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" onclick="saveTranslation()" class="btn btn-primary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Save Translation")</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="exportTranslation()">@translator.Translate(userLanguage, "Export Translation")</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
76
Views/Home/_Translations.cshtml
Normal file
76
Views/Home/_Translations.cshtml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
@using CarCareTracker.Helper
|
||||||
|
@inject IConfigHelper config
|
||||||
|
@inject ITranslationHelper translator
|
||||||
|
@model Translations
|
||||||
|
@{
|
||||||
|
var userConfig = config.GetUserConfig(User);
|
||||||
|
var userLanguage = userConfig.UserLanguage;
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="translationDownloaderModalLabel">@translator.Translate(userLanguage, "Available Translations")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideTranslationDownloader()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" onkeydown="handleEnter(this)">
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="form-group" style="max-height:50vh; overflow-x:hidden; overflow-y:scroll;">
|
||||||
|
@foreach(var translation in Model.Africa)
|
||||||
|
{
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-10">@StaticHelper.GetTranslationName(translation)</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="downloadTranslation('Africa','@translation')"><i class="bi bi-download"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@foreach (var translation in Model.Asia)
|
||||||
|
{
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-10">@StaticHelper.GetTranslationName(translation)</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="downloadTranslation('Asia','@translation')"><i class="bi bi-download"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@foreach (var translation in Model.Europe)
|
||||||
|
{
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-10">@StaticHelper.GetTranslationName(translation)</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="downloadTranslation('Europe','@translation')"><i class="bi bi-download"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@foreach (var translation in Model.NorthAmerica)
|
||||||
|
{
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-10">@StaticHelper.GetTranslationName(translation)</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="downloadTranslation('NorthAmerica','@translation')"><i class="bi bi-download"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@foreach (var translation in Model.SouthAmerica)
|
||||||
|
{
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-10">@StaticHelper.GetTranslationName(translation)</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="downloadTranslation('SouthAmerica','@translation')"><i class="bi bi-download"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@foreach (var translation in Model.Oceania)
|
||||||
|
{
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-10">@StaticHelper.GetTranslationName(translation)</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="downloadTranslation('Oceania','@translation')"><i class="bi bi-download"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideTranslationDownloader()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="downloadAllTranslations()">@translator.Translate(userLanguage, "Download All Translations")</button>
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user