first commit

This commit is contained in:
Eric Gullickson
2025-07-15 20:34:05 -05:00
commit f7eca4bad5
602 changed files with 158990 additions and 0 deletions

854
Views/API/Index.cshtml Normal file
View File

@@ -0,0 +1,854 @@
@using MotoVaultPro.Helper
@{
ViewData["Title"] = "API";
}
@inject IConfigHelper config
@section Nav {
<div class="container-fluid motovaultpro-navbar-container frosted hideOnPrint">
<div class="row mt-2">
<div class="d-flex motovaultpro-navbar">
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
<img src="@config.GetSmallLogoUrl()" class="motovaultpro-logo" />
</div>
<span class="text-truncate lead">API</span>
<span class="lead text-end flex-grow-1 d-none flex-wrap d-md-block">If authentication is enabled, use the credentials of the user for Basic Auth(RFC2617)</span>
</div>
</div>
<hr />
</div>
}
<div style="overflow-x:hidden;">
<div class="row">
<div class="col-1">
<h6>Method</h6>
</div>
<div class="col-5 copyable">
<h6>Endpoint</h6>
</div>
<div class="col-3">
<h6>Description</h6>
</div>
<div class="col-3">
<h6>Parameters</h6>
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/whoami</code>
</div>
<div class="col-3">
Returns information for current user
</div>
<div class="col-3">
No Params
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/version</code>
</div>
<div class="col-3">
Returns current version of MotoVaultPro and checks for updates
</div>
<div class="col-3">
CheckForUpdate(bool) - checks for update(optional)
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/vehicles</code>
</div>
<div class="col-3">
Returns a list of vehicles
</div>
<div class="col-3">
No Params
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/vehicle/info</code>
</div>
<div class="col-3">
Returns details for list of vehicles or specific vehicle
</div>
<div class="col-3">
VehicleId - Id of Vehicle(optional)
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/adjustedodometer</code>
</div>
<div class="col-3">
Returns odometer reading with adjustments applied
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
odometer - Unadjusted odometer
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords</code>
</div>
<div class="col-3">
Returns a list of odometer records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/latest</code>
</div>
<div class="col-3">
Returns last reported odometer for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/add</code>
</div>
<div class="col-3">
Adds Odometer Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
initialOdometer - Initial Odometer reading(optional)<br />
odometer - Odometer reading<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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/update</code>
</div>
<div class="col-3">
Updates Odometer Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Odometer Record<br />
date - Date to be entered<br />
initialOdometer - Initial Odometer reading<br />
odometer - Odometer reading<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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/delete</code>
</div>
<div class="col-3">
Deletes Odometer Record
</div>
<div class="col-3">
Id - Id of Odometer Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/planrecords</code>
</div>
<div class="col-3">
Returns a list of plan records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/planrecords/add</code>
</div>
<div class="col-3">
Adds Plan Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
description - Description<br />
cost - Cost<br />
type - ServiceRecord/RepairRecord/UpgradeRecord<br />
priority - Low/Normal/Critical<br />
progress - Backlog/InProgress/Testing<br />
notes - notes(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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/planrecords/update</code>
</div>
<div class="col-3">
Updates Plan Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Plan Record<br />
description - Description<br />
cost - Cost<br />
type - ServiceRecord/RepairRecord/UpgradeRecord<br />
priority - Low/Normal/Critical<br />
progress - Backlog/InProgress/Testing<br />
notes - notes(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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/planrecords/delete</code>
</div>
<div class="col-3">
Deletes Plan Record
</div>
<div class="col-3">
Id - Id of Plan Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/servicerecords</code>
</div>
<div class="col-3">
Returns a list of service records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/servicerecords/add</code>
</div>
<div class="col-3">
Adds Service Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
description - Description<br/>
cost - Cost<br />
notes - notes(optional)<br />
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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/servicerecords/update</code>
</div>
<div class="col-3">
Updates Service Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Service Record<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
description - Description<br />
cost - Cost<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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/servicerecords/delete</code>
</div>
<div class="col-3">
Deletes Service Record
</div>
<div class="col-3">
Id - Id of Service Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/repairrecords</code>
</div>
<div class="col-3">
Returns a list of repair records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/repairrecords/add</code>
</div>
<div class="col-3">
Adds Repair Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/repairrecords/update</code>
</div>
<div class="col-3">
Updates Repair Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Repair Record <br />
date - Date to be entered<br />
odometer - Odometer reading<br />
description - Description<br />
cost - Cost<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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/repairrecords/delete</code>
</div>
<div class="col-3">
Deletes Repair Record
</div>
<div class="col-3">
Id - Id of Repair Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/upgraderecords</code>
</div>
<div class="col-3">
Returns a list of upgrade records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/upgraderecords/add</code>
</div>
<div class="col-3">
Adds Upgrade Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/upgraderecords/update</code>
</div>
<div class="col-3">
Updates Upgrade Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Upgrade Record<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
description - Description<br />
cost - Cost<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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/upgraderecords/delete</code>
</div>
<div class="col-3">
Deletes Upgrade Record
</div>
<div class="col-3">
Id - Id of Upgrade Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/taxrecords</code>
</div>
<div class="col-3">
Returns a list of tax records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/vehicle/taxrecords/check</code>
</div>
<div class="col-3">
Updates Outdated Recurring Tax Records
</div>
<div class="col-3">
No Params
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/taxrecords/add</code>
</div>
<div class="col-3">
Adds Tax Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/taxrecords/update</code>
</div>
<div class="col-3">
Updates Tax Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Tax Record<br />
date - Date to be entered<br />
description - Description<br />
cost - Cost<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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/taxrecords/delete</code>
</div>
<div class="col-3">
Deletes Tax Record
</div>
<div class="col-3">
Id - Id of Tax Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/gasrecords</code>
</div>
<div class="col-3">
Returns a list of gas records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
useMPG(bool) - Use Imperial Units and Calculation
<br />
useUKMPG(bool) - Use UK Imperial Calculation
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/gasrecords/add</code>
</div>
<div class="col-3">
Adds Gas Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
fuelConsumed - Fuel Consumed<br />
cost - Cost<br />
isFillToFull(bool) - Filled To Full<br />
missedFuelUp(bool) - Missed Fuel Up<br />
notes - notes(optional)<br />
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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/gasrecords/update</code>
</div>
<div class="col-3">
Updates Gas Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Gas Record<br />
date - Date to be entered<br />
odometer - Odometer reading<br />
fuelConsumed - Fuel Consumed<br />
cost - Cost<br />
isFillToFull(bool) - Filled To Full<br />
missedFuelUp(bool) - Missed Fuel Up<br />
notes - notes(optional)<br />
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 />
files - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showAttachmentsInfo()">attachments(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/gasrecords/delete</code>
</div>
<div class="col-3">
Deletes Gas Record
</div>
<div class="col-3">
Id - Id of Gas Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/reminders</code>
</div>
<div class="col-3">
Returns a list of reminders for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/reminders/add</code>
</div>
<div class="col-3">
Adds Reminder Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
description - Description<br />
dueDate - Due Date<br />
dueOdometer - Due Odometer reading<br />
metric - Date/Odometer/Both<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-warning">PUT</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/reminders/update</code>
</div>
<div class="col-3">
Updates Reminder Record
</div>
<div class="col-3">
Body(form-data): {<br />
Id - Id of Reminder Record<br />
description - Description<br />
dueDate - Due Date<br />
dueOdometer - Due Odometer reading<br />
metric - Date/Odometer/Both<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge text-bg-danger">DELETE</span>
</div>
<div class="col-5 copyable">
<code>/api/vehicle/reminders/delete</code>
</div>
<div class="col-3">
Deletes Reminder Record
</div>
<div class="col-3">
Id - Id of Reminder Record
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/calendar</code>
</div>
<div class="col-3">
Returns reminder calendar in ICS format
</div>
<div class="col-3">
No Params
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-primary">POST</span>
</div>
<div class="col-5 copyable">
<code>/api/documents/upload</code>
</div>
<div class="col-3">
Upload Documents
</div>
<div class="col-3">
Body(form-data): {<br />
documents[] - Files to Upload<br />
}
</div>
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/vehicle/reminders/send</code>
</div>
<div class="col-3">
Send reminder emails out to collaborators based on specified urgency.
</div>
<div class="col-3">
(must be root user)<br />
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue](optional)
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/makebackup</code>
</div>
<div class="col-3">
Creates a snapshot/backup of the DB at the time and returns a URL to download it.
</div>
<div class="col-3">
No Params(must be root user)
</div>
</div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/cleanup</code>
</div>
<div class="col-3">
Clears out temp files. Deep clean will also delete unlinked thumbnails and documents. Returns number of deleted files.
</div>
<div class="col-3">
(must be root user)<br />
deepClean(bool) - Perform deep clean(optional)
</div>
</div>
}
</div>
<script>
$('.copyable').on('click', function (e) {
if (e.ctrlKey || e.metaKey){
let targetElement = $(e.currentTarget);
if (targetElement.hasClass("testable")){
window.location = targetElement.text().trim();
}
} else {
copyToClipboard(e.currentTarget);
}
})
function showExtraFieldsInfo(){
Swal.fire({
title: "Format for Extra Fields",
html: "extrafields[i][name] - Name of Extra Field<br/>extrafields[i][value] - Value of Extra Field<br/>i is an integer that starts at 0 and increments with each extrafield",
icon: "info"
});
}
function showAttachmentsInfo(){
Swal.fire({
title: "Attaching Files",
html: "The Document Upload Endpoint will upload the files and provide a formatted output which you can pass into this method",
icon: "info"
});
}
</script>

189
Views/Admin/Index.cshtml Normal file
View File

@@ -0,0 +1,189 @@
@using MotoVaultPro.Helper
@{
ViewData["Title"] = "Admin Panel";
}
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
bool emailServerIsSetup = true;
var mailConfig = config.GetMailConfig();
var userLanguage = config.GetServerLanguage();
if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
emailServerIsSetup = false;
}
}
@section Nav {
<div class="container-fluid motovaultpro-navbar-container frosted hideOnPrint">
<div class="row mt-2">
<div class="d-flex motovaultpro-navbar">
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
<img src="@config.GetSmallLogoUrl()" class="motovaultpro-logo" />
</div>
<span class="text-truncate lead">@translator.Translate(userLanguage, "Admin Panel")</span>
</div>
</div>
<hr />
</div>
}
@model AdminViewModel
<div class="container">
<div class="row">
<div class="col-12">
<div class="row">
<div class="col-2 d-flex align-items-center">
<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 />
<table class="table table-hover">
<thead class="sticky-top sticky-top-nav">
<tr class="d-flex">
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Username")</th>
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Email")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Is Admin")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody id="userTable">
@await Html.PartialAsync("_Users", Model.Users)
</tbody>
</table>
</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>
<script>
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");
$.post('/Admin/UpdateUserAdminStatus', { userId: userId, isAdmin: isChecked }, function (data) {
if (data) {
reloadUserTable();
} else {
errorToast(genericErrorMessage());
}
});
}
function toggleAutoNotify(e) {
if (!$("#enableAutoNotify").attr("disabled")) {
if ($(e.target).hasClass('btn')) {
$("#enableAutoNotify").trigger('click');
}
}
}
function deleteToken(tokenId) {
$.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) {
if (data) {
reloadTokenTable();
} else {
errorToast(genericErrorMessage());
}
});
}
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) {
if (data) {
reloadUserTable();
} else {
errorToast(genericErrorMessage());
}
});
}
});
}
function generateNewToken() {
Swal.fire({
title: 'Generate Token',
html: `
<input type="text" id="inputEmail" class="swal2-input" placeholder="Email Address" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Generate',
focusConfirm: false,
preConfirm: () => {
const emailAddress = $("#inputEmail").val();
if (!emailAddress) {
Swal.showValidationMessage(`Please enter an email address`)
}
return { emailAddress }
},
}).then(function (result) {
if (result.isConfirmed) {
var autoNotify = $("#enableAutoNotify").is(":checked");
$.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) {
if (data.success) {
reloadTokenTable();
} else {
errorToast(data.message)
}
});
}
});
}
</script>

View File

@@ -0,0 +1,12 @@
@using MotoVaultPro.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
View File

@@ -0,0 +1,11 @@
@using MotoVaultPro.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>
}

172
Views/Home/Index.cshtml Normal file
View File

@@ -0,0 +1,172 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model string
@{
ViewData["Title"] = "Garage";
}
@section Scripts {
<script src="~/js/garage.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/settings.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
@section Nav {
<div class="container-fluid motovaultpro-navbar-container frosted hideOnPrint">
<div class="row mt-2">
<div class="d-flex motovaultpro-navbar">
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
<img src="@config.GetSmallLogoUrl()" class="motovaultpro-logo motovaultpro-tab" />
<img src="@config.GetLogoUrl()" class="motovaultpro-logo motovaultpro-mobile-nav-show" />
</div>
<ul class="nav nav-tabs motovaultpro-tab flex-grow-1" id="homeTab" role="tablist">
<li class="nav-item" role="presentation">
<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">@translator.Translate(userLanguage, "Garage")</span></button>
</li>
@if (config.GetServerEnableShopSupplies())
{
<li class="nav-item" role="presentation">
<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">@translator.Translate(userLanguage, "Supplies")</span></button>
</li>
}
@if (userConfig.ShowCalendar)
{
<li class="nav-item" role="presentation">
<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">@translator.Translate(userLanguage, "Calendar")</span></button>
</li>
}
<li class="nav-item dropdown nav-item-persist nav-item-more me-5" role="presentation" style="display:none;">
<a class="nav-link resizable-nav-link" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-three-dots"></i></a>
<ul class="dropdown-menu">
</ul>
</li>
<li class="nav-item ms-auto" role="presentation">
<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">@translator.Translate(userLanguage, "Settings")</span></button>
</li>
@if (User.IsInRole("CookieAuth") || User.IsInRole("APIAuth"))
{
<li class="nav-item dropdown nav-item-persist" role="presentation">
<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">@User.Identity.Name</span></a>
<ul class="dropdown-menu">
@if (User.IsInRole(nameof(UserData.IsAdmin)))
{
<li>
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage, "Admin Panel")</a>
</li>
}
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<li>
<button class="dropdown-item" onclick="showRootAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
</li>
}
else
{
<li>
<button class="dropdown-item" onclick="showAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
</li>
}
<li>
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage, "Logout")</button>
</li>
</ul>
</li>
}
</ul>
<div class="motovaultpro-navbar-button">
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list motovaultpro-menu-icon"></i></button>
</div>
</div>
</div>
<hr />
</div>
}
<div class="motovaultpro-mobile-nav" onclick="hideMobileNav()">
<ul class="navbar-nav" id="homeTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link user-select-none @(Model == "garage" ? "active" : "")" ontouchstart="detectLongTouch(this)" ontouchend="detectTouchEndPremature(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-car-front me-2"></i>@translator.Translate(userLanguage,"Garage")</span></button>
</li>
@if(config.GetServerEnableShopSupplies())
{
<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"><span class="ms-2 display-3"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</span></button>
</li>
}
@if(userConfig.ShowCalendar){
<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"><span class="ms-2 display-3"><i class="bi bi-calendar-week me-2"></i>@translator.Translate(userLanguage, "Calendar")</span></button>
</li>
}
<li class="nav-item" 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"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</span></button>
</li>
@if (User.IsInRole("CookieAuth") || User.IsInRole("APIAuth"))
{
@if (User.IsInRole(nameof(UserData.IsAdmin)))
{
<li class="nav-item" role="presentation">
<a class="dropdown-item" href="/Admin"><span class="display-3 ms-2"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</span></a>
</li>
}
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<li>
<button class="nav-link" onclick="showRootAccountInformationModal()"><span class="display-3 ms-2"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</span></button>
</li>
} else
{
<li>
<button class="nav-link" onclick="showAccountInformationModal()"><span class="display-3 ms-2"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</span></button>
</li>
}
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="performLogOut()"><span class="display-3 ms-2"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</span></button>
</li>
}
</ul>
</div>
<div class="container">
<div class="tab-content" id="homeTab">
<div class="tab-pane fade @(Model == "garage" ? "show active" : "")" id="garage-tab-pane" role="tabpanel" tabindex="0">
<div id="garageContainer">
</div>
</div>
<div class="tab-pane fade" id="supply-tab-pane" role="tabpanel" tabindex="0">
</div>
<div class="tab-pane fade" id="calendar-tab-pane" role="tabpanel" tabindex="0">
</div>
<div class="tab-pane fade @(Model == "settings" ? "show active" : "")" id="settings-tab-pane" role="tabpanel" tabindex="0">
</div>
</div>
</div>
<div class="modal fade" id="addVehicleModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="addVehicleModalContent">
</div>
</div>
</div>
<div class="modal fade" id="bulkImportModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content" id="bulkImportModalContent">
</div>
</div>
</div>
<div class="modal fade" id="accountInformationModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content" id="accountInformationModalContent">
</div>
</div>
</div>
<div class="stickerPrintContainer hideOnPrint">
</div>
<script>
loadGarage();
bindWindowResize();
checkNavBarOverflow();
</script>

157
Views/Home/Kiosk.cshtml Normal file
View File

@@ -0,0 +1,157 @@
@{
ViewData["Title"] = "Kiosk";
}
@model KioskViewModel
@section Scripts {
<script src="~/lib/masonry/masonry.min.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
<div class="no-top-pad">
<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>
</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');
var reminderNoteText = reminderNote.text().trim();
if (reminderNoteText != ''){
if (reminderNote.hasClass('d-none')) {
reminderNote.removeClass('d-none');
if (!reminderNote.hasClass('reminder-note-markdown')){
let markedDownReminderNote = markdown(reminderNoteText);
reminderNote.html(markedDownReminderNote);
reminderNote.addClass('reminder-note-markdown');
}
} 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>

276
Views/Home/Setup.cshtml Normal file
View File

@@ -0,0 +1,276 @@
@using MotoVaultPro.Helper
@{
ViewData["Title"] = "Server Settings";
}
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
bool emailServerIsSetup = true;
var mailConfig = config.GetMailConfig();
var userLanguage = config.GetServerLanguage();
if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
emailServerIsSetup = false;
}
}
@section Scripts {
<script src="~/js/serversettings.js?v=@StaticHelper.VersionNumber"></script>
}
@section Nav {
<div class="container-fluid motovaultpro-navbar-container frosted hideOnPrint">
<div class="row mt-2">
<div class="d-flex motovaultpro-navbar">
<div style="cursor:pointer;" onclick="returnToGarage()" class="me-2">
<img src="@config.GetSmallLogoUrl()" class="motovaultpro-logo" />
</div>
<span class="text-truncate lead">@translator.Translate(userLanguage, "Server Settings Configurator")</span>
</div>
</div>
<hr />
</div>
}
@model ServerSettingsViewModel
<div class="setup-wizard-container frosted">
<div class="setup-wizard-content" data-page="0">
<div class="d-flex text-center align-items-center justify-content-center flex-column" style="height:100%;">
<div><span class="display-5">@translator.Translate(userLanguage, "Server Settings Configurator")</span></div>
<div><span class="lead">@translator.Translate(userLanguage, "By proceeding, you acknowledge that you are solely responsible for all consequences from utilizing the Server Settings Configurator")</span></div>
<div class="mt-2"><a class="btn btn-primary" onclick="nextSetupPage()">@translator.Translate(userLanguage, "Acknowledge and Continue")</a></div>
</div>
</div>
<div class="setup-wizard-content" data-page="1" style="display:none;">
<span class="display-5">@translator.Translate(userLanguage, "Server Settings")</span>
<hr />
<form class="form-inline">
<div class="form-group">
<label for="inputPostgres">@translator.Translate(userLanguage, "Postgres Connection")</label>
<div class="input-group">
<input type="text" id="inputPostgres" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.PostgresConnection">
<div class="input-group-text">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="skipPostgres">
<label class="form-check-label" for="skipPostgres">@translator.Translate(userLanguage, "Skip")</label>
</div>
</div>
</div>
<small class="text-body-secondary">@translator.Translate(userLanguage, "Restart Required")</small>
</div>
<div class="form-group">
<label for="inputFileExt">@translator.Translate(userLanguage, "Allowed File Extensions")</label>
<input type="text" id="inputFileExt" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.AllowedFileExtensions">
</div>
<div class="form-group">
<label for="inputLogoURL">@translator.Translate(userLanguage, "Logo URL")</label>
<input type="text" id="inputLogoURL" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.CustomLogoURL">
</div>
<div class="form-group">
<label for="inputSmallLogoURL">@translator.Translate(userLanguage, "Small Logo URL")</label>
<input type="text" id="inputSmallLogoURL" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.CustomSmallLogoURL">
</div>
<div class="form-group">
<label for="inputMOTD">@translator.Translate(userLanguage, "Message of the Day")</label>
<input type="text" id="inputMOTD" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.MessageOfTheDay">
</div>
<div class="form-group">
<label for="inputWebHook">@translator.Translate(userLanguage, "WebHook URL")</label>
<input type="text" id="inputWebHook" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.WebHookURL">
</div>
<div class="form-group">
<label for="inputDomain">@translator.Translate(userLanguage, "Server URL")</label>
<input type="text" id="inputDomain" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.Domain">
</div>
<div class="form-group">
<label for="inputCustomWidget">@translator.Translate(userLanguage, "Custom Widgets")</label>
<select class="form-select" id="inputCustomWidget">
<!option value="true" @(Model.CustomWidgetsEnabled ? "selected" : "")>@translator.Translate(userLanguage, "Enabled")</!option>
<!option value="false" @(Model.CustomWidgetsEnabled ? "" : "selected")>@translator.Translate(userLanguage, "Disabled")</!option>
</select>
</div>
<div class="form-group">
<label for="inputInvariantAPI">@translator.Translate(userLanguage, "Invariant API")</label>
<select class="form-select" id="inputInvariantAPI">
<!option value="true" @(Model.InvariantAPIEnabled ? "selected" : "")>@translator.Translate(userLanguage, "Enabled")</!option>
<!option value="false" @(Model.InvariantAPIEnabled ? "" : "selected")>@translator.Translate(userLanguage, "Disabled")</!option>
</select>
</div>
</form>
</div>
<div class="setup-wizard-content" data-page="2" style="display:none;">
<div class="d-flex justify-content-between align-items-center">
<span class="display-5">@translator.Translate(userLanguage, "SMTP")</span>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onchange="nextOnSkip(this)" role="switch" id="skipSMTP">
<label class="form-check-label" for="skipSMTP">@translator.Translate(userLanguage, "Skip")</label>
</div>
</div>
<hr />
<form class="form-inline">
<div class="form-group">
<label for="inputSMTPServer">@translator.Translate(userLanguage, "SMTP Server")</label>
<input type="text" id="inputSMTPServer" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.EmailServer">
</div>
<div class="form-group">
<label for="inputSMTPPort">@translator.Translate(userLanguage, "SMTP Server Port")</label>
<input type="text" id="inputSMTPPort" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.Port">
</div>
<div class="form-group">
<label for="inputSMTPFrom">@translator.Translate(userLanguage, "SMTP Sender Address")</label>
<input type="text" id="inputSMTPFrom" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.EmailFrom">
</div>
<div class="form-group">
<label for="inputSMTPUsername">@translator.Translate(userLanguage, "SMTP Username")</label>
<input type="text" id="inputSMTPUsername" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.Username">
</div>
<div class="form-group">
<label for="inputSMTPPassword">@translator.Translate(userLanguage, "SMTP Password")</label>
<div class="input-group">
<input type="password" id="inputSMTPPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.SMTPConfig.Password">
<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 class="form-group">
<div class="d-flex align-items-center justify-content-center flex-column">
<div class="mt-2"><a class="btn btn-warning" onclick="sendTestEmail()">@translator.Translate(userLanguage, "Test SMTP Settings")</a></div>
</div>
</div>
</form>
</div>
<div class="setup-wizard-content" data-page="3" style="display:none;">
<div class="d-flex justify-content-between align-items-center">
<span class="display-5">@translator.Translate(userLanguage, "Single Sign On")</span>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onchange="nextOnSkip(this)" role="switch" id="skipOIDC">
<label class="form-check-label" for="skipOIDC">@translator.Translate(userLanguage, "Skip")</label>
</div>
</div>
<hr />
<form class="form-inline">
<div class="form-group">
<label for="inputOIDCProvider">@translator.Translate(userLanguage, "OIDC Provider")</label>
<input type="text" id="inputOIDCProvider" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.Name">
</div>
<div class="form-group">
<label for="inputOIDCClient">@translator.Translate(userLanguage, "OIDC Client ID")</label>
<input type="text" id="inputOIDCClient" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.ClientId">
</div>
<div class="form-group">
<label for="inputOIDCSecret">@translator.Translate(userLanguage, "OIDC Client Secret")</label>
<div class="input-group">
<input type="password" id="inputOIDCSecret" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.ClientSecret">
<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 class="form-group">
<label for="inputOIDCAuth">@translator.Translate(userLanguage, "OIDC Auth URL")</label>
<input type="text" id="inputOIDCAuth" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.AuthURL">
</div>
<div class="form-group">
<label for="inputOIDCToken">@translator.Translate(userLanguage, "OIDC Token URL")</label>
<input type="text" id="inputOIDCToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.TokenURL">
</div>
<div class="form-group">
<label for="inputOIDCUserInfo">@translator.Translate(userLanguage, "OIDC UserInfo URL")</label>
<input type="text" id="inputOIDCUserInfo" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.UserInfoURL">
</div>
<div class="form-group">
<label for="inputOIDCRedirect">@translator.Translate(userLanguage, "OIDC Redirect URL")</label>
<input type="text" id="inputOIDCRedirect" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.RedirectURL">
</div>
<div class="form-group">
<label for="inputOIDCScope">@translator.Translate(userLanguage, "OIDC Scope")</label>
<input type="text" id="inputOIDCScope" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.Scope">
</div>
<div class="form-group">
<label for="inputOIDCLogout">@translator.Translate(userLanguage, "OIDC Logout URL")</label>
<input type="text" id="inputOIDCLogout" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.OIDCConfig.LogOutURL">
</div>
<div class="form-group">
<label for="inputOIDCState">@translator.Translate(userLanguage, "OIDC Validate State")</label>
<select class="form-select" id="inputOIDCState">
<!option value="true" @(Model.OIDCConfig.ValidateState ? "selected" : "")>@translator.Translate(userLanguage, "Enabled")</!option>
<!option value="false" @(Model.OIDCConfig.ValidateState ? "" : "selected")>@translator.Translate(userLanguage, "Disabled")</!option>
</select>
</div>
<div class="form-group">
<label for="inputOIDCPKCE">@translator.Translate(userLanguage, "OIDC Use PKCE")</label>
<select class="form-select" id="inputOIDCPKCE">
<!option value="true" @(Model.OIDCConfig.UsePKCE ? "selected" : "")>@translator.Translate(userLanguage, "Enabled")</!option>
<!option value="false" @(Model.OIDCConfig.UsePKCE ? "" : "selected")>@translator.Translate(userLanguage, "Disabled")</!option>
</select>
</div>
<div class="form-group">
<label for="inputOIDCDisable">@translator.Translate(userLanguage, "OIDC Login Only")</label>
<select class="form-select" id="inputOIDCDisable">
<!option value="true" @(Model.OIDCConfig.DisableRegularLogin ? "selected" : "")>@translator.Translate(userLanguage, "Enabled")</!option>
<!option value="false" @(Model.OIDCConfig.DisableRegularLogin ? "" : "selected")>@translator.Translate(userLanguage, "Disabled")</!option>
</select>
</div>
</form>
</div>
<div class="setup-wizard-content" data-page="4" style="display:none;">
<span class="display-5">@translator.Translate(userLanguage, "Miscellaneous")</span>
<hr />
<form class="form-inline">
@if (Model.EnableAuth)
{
<div class="form-group">
<label for="inputRegistrationMode">@translator.Translate(userLanguage, "Registration")</label>
<select class="form-select" id="inputRegistrationMode">
<!option value="0" @(!Model.OpenRegistration && !Model.DisableRegistration ? "selected" : "")>@translator.Translate(userLanguage, "Invitation Only")</!option>
<!option value="1" @(Model.DisableRegistration ? "selected" : "")>@translator.Translate(userLanguage, "Disable Registration")</!option>
<!option value="2" @(Model.OpenRegistration ? "selected" : "")>@translator.Translate(userLanguage, "Open Registration")</!option>
</select>
</div>
<div class="form-group">
<label for="inputOIDCRootUser">@translator.Translate(userLanguage, "Enable OIDC for Root User")</label>
<select class="form-select" id="inputOIDCRootUser">
<!option value="true" @(Model.EnableRootUserOIDC ? "selected" : "")>@translator.Translate(userLanguage, "Enabled")</!option>
<!option value="false" @(Model.EnableRootUserOIDC ? "" : "selected")>@translator.Translate(userLanguage, "Disabled")</!option>
</select>
</div>
}
<div class="form-group">
<label for="inputDefaultReminderEmail">@translator.Translate(userLanguage, "Root User Email Address")</label>
<input type="text" id="inputDefaultReminderEmail" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.DefaultReminderEmail">
</div>
<hr />
<div class="mb-2">
<span class="display-6">@translator.Translate(userLanguage, "Reminder Urgency Thresholds")</span>
</div>
<div class="form-group">
<label for="inputUrgentDays">@translator.Translate(userLanguage, "Urgent(Days)")</label>
<input type="number" inputmode="numeric" id="inputUrgentDays" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.ReminderUrgencyConfig.UrgentDays">
</div>
<div class="form-group">
<label for="inputVeryUrgentDays">@translator.Translate(userLanguage, "Very Urgent(Days)")</label>
<input type="number" inputmode="numeric" id="inputVeryUrgentDays" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.ReminderUrgencyConfig.VeryUrgentDays">
</div>
<div class="form-group">
<label for="inputUrgentDistance">@translator.Translate(userLanguage, "Urgent(Distance)")</label>
<input type="number" inputmode="numeric" id="inputUrgentDistance" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.ReminderUrgencyConfig.UrgentDistance">
</div>
<div class="form-group">
<label for="inputVeryUrgentDistance">@translator.Translate(userLanguage, "Very Urgent(Distance)")</label>
<input type="number" inputmode="numeric" id="inputVeryUrgentDistance" class="form-control" placeholder="@translator.Translate(userLanguage, "Not Configured")" value="@Model.ReminderUrgencyConfig.VeryUrgentDistance">
</div>
</form>
</div>
<div class="setup-wizard-content" data-page="5" style="display:none;">
<div class="d-flex text-center align-items-center justify-content-center flex-column" style="height:100%;">
<div><span class="display-5">@translator.Translate(userLanguage, "Server Settings Saved")<span class="text-success"><i class="bi bi-check-lg"></i></span></span></div>
<div class="mt-2"><a class="btn btn-secondary me-2" onclick="loadSetupPage(0)">@translator.Translate(userLanguage, "Restart Wizard")</a><a class="btn btn-primary" onclick="returnToGarage()">@translator.Translate(userLanguage, "Return to Garage")</a></div>
</div>
</div>
<div class="setup-wizard-nav" style="display:none;">
<div class="d-flex align-items-center justify-content-between pe-2" style="height:100%;">
<a class="btn btn-prev btn-link" onclick="previousSetupPage()">@translator.Translate(userLanguage, "Back")</a>
<a class="btn btn-next btn-primary" onclick="nextSetupPage()">@translator.Translate(userLanguage, "Next")</a>
<a class="btn btn-save btn-success" onclick="saveSetup()">@translator.Translate(userLanguage, "Save")</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,40 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model UserData
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title" id="updateAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form class="form-inline">
<div class="form-group">
<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">
<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">
<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="">
<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>
<input type="text" id="inputToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Token")" value="">
<div class="row">
<div class="col-12">
<a onclick="generateTokenForUser()" class="btn btn-link">@translator.Translate(userLanguage, "Send Token")</a>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideAccountInformationModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="validateAndSaveUserAccount()" class="btn btn-primary">@translator.Translate(userLanguage, "Update")</button>
</div>

View File

@@ -0,0 +1,25 @@
@using MotoVaultPro.Helper
@model List<ReminderRecordViewModel>
<script>
var eventDates = [];
var groupedDates = [];
@foreach(ReminderRecordViewModel reminderRecord in Model)
{
@:eventDates.push({ id: @reminderRecord.Id, date: decodeHTMLEntities('@StaticHelper.GetEpochFromDateTime(reminderRecord.Date)'), description: decodeHTMLEntities('@reminderRecord.Description'), urgency: decodeHTMLEntities('@reminderRecord.Urgency.ToString()') });
}
</script>
<div class="row vehicleDetailTabContainer">
<div class="col-12 reminderCalendarView">
<div class="reminderCalendarViewContent" style="height:60vh;">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="reminderRecordCalendarModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="reminderRecordCalendarModalContent">
</div>
</div>
</div>
<script>
initCalendar();
</script>

View File

@@ -0,0 +1,143 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model RecordExtraField
<script>
var extraFields = [];
</script>
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "Manage Extra Fields"))</h5>
<button type="button" class="btn-close" onclick="hideExtraFieldModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<div class="row">
<div class="col-12">
<select class="form-select" onchange="refreshExtraFieldModal()" id="extraFieldTab">
<!option @(Model.Id == (int)ImportMode.ServiceRecord ? "selected" : "") value="@((int)ImportMode.ServiceRecord)">@translator.Translate(userLanguage, "Service Record")</!option>
<!option @(Model.Id == (int)ImportMode.RepairRecord ? "selected" : "") value="@((int)ImportMode.RepairRecord)">@translator.Translate(userLanguage, "Repairs")</!option>
<!option @(Model.Id == (int)ImportMode.UpgradeRecord ? "selected" : "") value="@((int)ImportMode.UpgradeRecord)">@translator.Translate(userLanguage, "Upgrades")</!option>
<!option @(Model.Id == (int)ImportMode.GasRecord ? "selected" : "") value="@((int)ImportMode.GasRecord)">@translator.Translate(userLanguage, "Fuel")</!option>
<!option @(Model.Id == (int)ImportMode.TaxRecord ? "selected" : "") value="@((int)ImportMode.TaxRecord)">@translator.Translate(userLanguage, "Tax")</!option>
<!option @(Model.Id == (int)ImportMode.SupplyRecord ? "selected" : "") value="@((int)ImportMode.SupplyRecord)">@translator.Translate(userLanguage, "Supplies")</!option>
<!option @(Model.Id == (int)ImportMode.PlanRecord ? "selected" : "") value="@((int)ImportMode.PlanRecord)">@translator.Translate(userLanguage, "Planner")</!option>
<!option @(Model.Id == (int)ImportMode.OdometerRecord ? "selected" : "") value="@((int)ImportMode.OdometerRecord)">@translator.Translate(userLanguage, "Odometer")</!option>
<!option @(Model.Id == (int)ImportMode.NoteRecord ? "selected" : "") value="@((int)ImportMode.NoteRecord)">@translator.Translate(userLanguage, "Notes")</!option>
<!option @(Model.Id == (int)ImportMode.VehicleRecord ? "selected" : "") value="@((int)ImportMode.VehicleRecord)">@translator.Translate(userLanguage, "Vehicle")</!option>
</select>
</div>
</div>
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-5">@translator.Translate(userLanguage, "Name")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Required")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Type")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody>
@if (Model.ExtraFields != null)
{
@foreach (ExtraField extraField in Model.ExtraFields)
{
<script>
extraFields.push({ name: decodeHTMLEntities('@extraField.Name'), isRequired: @extraField.IsRequired.ToString().ToLower(), fieldType: decodeHTMLEntities('@extraField.FieldType')});
</script>
<tr class="d-flex">
<td class="col-5">@extraField.Name</td>
<td class="col-2"><input class="form-check-input" type="checkbox" onchange="updateExtraFieldIsRequired(decodeHTMLEntities('@extraField.Name'), this)" value="" @(extraField.IsRequired ? "checked" : "") /></td>
<td class="col-3">
<select class="form-select" onchange="updateExtraFieldType(decodeHTMLEntities('@extraField.Name'), this)">
<!option @(extraField.FieldType == ExtraFieldType.Text ? "selected" : "") value="@((int)ExtraFieldType.Text)">@translator.Translate(userLanguage, "Text")</!option>
<!option @(extraField.FieldType == ExtraFieldType.Number ? "selected" : "") value="@((int)ExtraFieldType.Number)">@translator.Translate(userLanguage, "Number")</!option>
<!option @(extraField.FieldType == ExtraFieldType.Decimal ? "selected" : "") value="@((int)ExtraFieldType.Decimal)">@translator.Translate(userLanguage, "Decimal")</!option>
<!option @(extraField.FieldType == ExtraFieldType.Date ? "selected" : "") value="@((int)ExtraFieldType.Date)">@translator.Translate(userLanguage, "Date")</!option>
<!option @(extraField.FieldType == ExtraFieldType.Time ? "selected" : "") value="@((int)ExtraFieldType.Time)">@translator.Translate(userLanguage, "Time")</!option>
<!option @(extraField.FieldType == ExtraFieldType.Location ? "selected" : "") value="@((int)ExtraFieldType.Location)">@translator.Translate(userLanguage, "Location")</!option>
</select>
</td>
<td class="col-2"><button type="button" onclick="deleteExtraField(decodeHTMLEntities('@extraField.Name'))" class="btn btn-danger"><i class="bi bi-trash"></i></button></td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideExtraFieldModal()">@translator.Translate(userLanguage, "Close")</button>
<button type="button" class="btn btn-primary" onclick="addExtraField()">@translator.Translate(userLanguage, "Add New Field")</button>
</div>
<script>
var importMode = @Model.Id;
function addExtraField() {
Swal.fire({
title: 'Field Name',
html: `
<input type="text" id="inputExtraFieldName" class="swal2-input" placeholder="Field Name" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Add',
focusConfirm: false,
preConfirm: () => {
const extraFieldName = $("#inputExtraFieldName").val();
if (!extraFieldName || extraFieldName.trim() == '') {
Swal.showValidationMessage(`Field names cannot be blank`);
} else if (extraFields.filter(x => x.name == extraFieldName).length > 0) {
Swal.showValidationMessage(`Field names must be unique`);
}
return { extraFieldName }
},
}).then(function (result) {
if (result.isConfirmed) {
extraFields.push({ name: result.value.extraFieldName, isRequired: false});
updateExtraFields();
}
});
}
function updateExtraFieldIsRequired(fieldId, checkbox){
var indexToEdit = extraFields.findIndex(x => x.name == fieldId);
var extraFieldToEdit = extraFields[indexToEdit];
extraFieldToEdit.isRequired = $(checkbox).is(":checked");
updateExtraFields();
}
function updateExtraFieldType(fieldId, dropDown){
var indexToEdit = extraFields.findIndex(x => x.name == fieldId);
var extraFieldToEdit = extraFields[indexToEdit];
extraFieldToEdit.fieldType = $(dropDown).val();
updateExtraFields();
}
function deleteExtraField(fieldId) {
extraFields = extraFields.filter(x => x.name != fieldId);
updateExtraFields();
}
function refreshExtraFieldModal() {
var selectedExtraFieldTab = $("#extraFieldTab").val();
$.post(`/Home/GetExtraFieldsModal?importMode=${selectedExtraFieldTab}`, function (data) {
if (data) {
$("#extraFieldModalContent").html(data);
} else {
errorToast(genericErrorMessage());
}
});
}
function updateExtraFields() {
$.post('/Home/UpdateExtraFields', { id: importMode, extraFields: extraFields }, function (data) {
if (data) {
$("#extraFieldModalContent").html(data);
} else {
errorToast(genericErrorMessage());
}
});
}
</script>

View File

@@ -0,0 +1,89 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<VehicleViewModel>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
}
@if (recordTags.Any())
{
<div class='row'>
<div class="col-12 d-flex align-items-center flex-wrap mb-2">
@foreach (string recordTag in recordTags)
{
<span onclick="filterGarage(this)" class="user-select-none ms-1 me-1 mt-1 mb-1 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
</div>
}
<div class="row gy-3 align-items-stretch vehiclesContainer pb-2 @(recordTags.Any() ? "" : "mt-2")">
@foreach (VehicleViewModel vehicle in Model)
{
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
{
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 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)">
<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))
{
<div class="vehicle-sold-banner"><p class='display-6 mb-0'>@translator.Translate(userLanguage, "SOLD")</p></div>
} else if (vehicle.DashboardMetrics.Any())
{
<div class="vehicle-sold-banner">
@if (vehicle.DashboardMetrics.Contains(DashboardMetric.Default) && vehicle.LastReportedMileage != default)
{
<div class="d-flex justify-content-between">
<div>
<span class="ms-2"><i class="bi bi-speedometer me-2"></i>@vehicle.LastReportedMileage.ToString("N0")</span>
</div>
@if (vehicle.HasReminders)
{
<div>
<span class="me-2"><i class="bi bi bi-bell-fill text-warning"></i></span>
</div>
}
</div>
}
@if (vehicle.DashboardMetrics.Contains(DashboardMetric.CostPerMile) && vehicle.CostPerMile != default)
{
<div class="d-flex justify-content-between">
<div>
<span class="ms-2"><i class="bi bi-cash-coin me-2"></i>@($"{vehicle.CostPerMile.ToString("C2")}/{vehicle.DistanceUnit}")</span>
</div>
</div>
}
@if (vehicle.DashboardMetrics.Contains(DashboardMetric.TotalCost) && vehicle.TotalCost != default)
{
<div class="d-flex justify-content-between">
<div>
<span class="ms-2"><i class="bi bi-cash-coin me-2"></i>@($"{vehicle.TotalCost.ToString("C2")}")</span>
</div>
</div>
}
</div>
}
<div class="card-body">
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>
<h5 class="card-title text-truncate">@($"{vehicle.Make}")</h5>
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5>
<p class="card-text text-truncate">@StaticHelper.GetVehicleIdentifier(vehicle)</p>
</div>
</div>
</div>
}
}
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 garage-item-add user-select-none">
<div class="card" onclick="showAddVehicleModal()" style="height:100%;">
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;pointer-events:none;" />
</div>
</div>
</div>

126
Views/Home/_Kiosk.cshtml Normal file
View File

@@ -0,0 +1,126 @@
@using MotoVaultPro.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>
}

View File

@@ -0,0 +1,86 @@
@using MotoVaultPro.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>
}

View File

@@ -0,0 +1,47 @@
@using MotoVaultPro.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>
}

View File

@@ -0,0 +1,39 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model ReminderRecordViewModel
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "View Reminder"))</h5>
<button type="button" class="btn-close" onclick="hideCalendarReminderModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12" id="reminderOptions">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="reminderDescription">@translator.Translate(userLanguage, "Date")</label>
<input type="text" id="reminderDescription" readonly class="form-control" value="@Model.Date.ToShortDateString()">
<label for="reminderDescription">@translator.Translate(userLanguage,"Description")</label>
<input type="text" id="reminderDescription" readonly class="form-control" value="@Model.Description">
</div>
<div class="col-md-6 col-12">
<label for="reminderNotes">@translator.Translate(userLanguage,"Notes")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="reminderNotes" readonly class="form-control" rows="5">@Model.Notes</textarea>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" onclick="deleteCalendarReminderRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
<button type="button" class="btn btn-secondary" onclick="hideCalendarReminderModal()">@translator.Translate(userLanguage, "Close")</button>
@if (Model.IsRecurring && (Model.Urgency == ReminderUrgency.VeryUrgent || Model.Urgency == ReminderUrgency.PastDue))
{
<button type="button" class="btn btn-primary" onclick="markDoneCalendarReminderRecord(@Model.Id)">@translator.Translate(userLanguage, "Mark as Done")</button>
}
</div>

View File

@@ -0,0 +1,31 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model UserData
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title" id="updateRootAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form class="form-inline">
<div class="form-group">
<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">
<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="">
<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>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideAccountInformationModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="validateAndSaveRootUserAccount()" class="btn btn-primary">@translator.Translate(userLanguage, "Update")</button>
</div>

426
Views/Home/_Settings.cshtml Normal file
View File

@@ -0,0 +1,426 @@
@using MotoVaultPro.Helper
@model SettingsViewModel
@inject ITranslationHelper translator
@{
var userLanguage = Model.UserConfig.UserLanguage;
}
<div class="row">
<div class="d-flex justify-content-center">
<h6 class="display-6 mt-2">@translator.Translate(userLanguage, "Settings")</h6>
</div>
<hr />
<div class="col-12 col-md-6">
<input id="preferredGasUnit" style="display:none;" value="@Model.UserConfig.PreferredGasUnit" />
<input id="preferredFuelMileageUnit" style="display:none;" value="@Model.UserConfig.PreferredGasMileageUnit" />
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateColorModeSettings(this)" type="checkbox" role="switch" id="enableDarkMode" checked="@Model.UserConfig.UseDarkMode">
<label class="form-check-label" for="enableDarkMode">@translator.Translate(userLanguage, "Dark Mode")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateColorModeSettings(this)" type="checkbox" role="switch" id="useSystemColorMode" checked="@Model.UserConfig.UseSystemColorMode">
<label class="form-check-label" for="useSystemColorMode">@translator.Translate(userLanguage, "Adaptive Color Mode")</label>
</div>
</div>
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableCsvImports" checked="@Model.UserConfig.EnableCsvImports">
<label class="form-check-label" for="enableCsvImports">@translator.Translate(userLanguage, "Enable CSV Imports")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UserConfig.UseDescending">
<label class="form-check-label" for="useDescending">@translator.Translate(userLanguage, "Sort lists in Descending Order(Newest to Oldest)")</label>
</div>
</div>
<div>
<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">
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 Costs with ---")</label>
</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>
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMarkDownOnSavedNotes" checked="@Model.UserConfig.UseMarkDownOnSavedNotes">
<label class="form-check-label" for="useMarkDownOnSavedNotes">@translator.Translate(userLanguage, "Display Saved Notes in Markdown")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableExtraFieldColumns" checked="@Model.UserConfig.EnableExtraFieldColumns">
<label class="form-check-label" for="enableExtraFieldColumns">@translator.Translate(userLanguage, "Show Extra Field Columns")</label>
</div>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="showVehicleThumbnail" checked="@Model.UserConfig.ShowVehicleThumbnail">
<label class="form-check-label" for="showVehicleThumbnail">@translator.Translate(userLanguage, "Show Vehicle Thumbnail in Header")</label>
</div>
<hr />
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useMPG" checked="@Model.UserConfig.UseMPG">
<label class="form-check-label" for="useMPG">@translator.Translate(userLanguage, "Use Imperial Calculation for Fuel Economy Calculations(MPG)")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "This Will Also Change Units to Miles and Gallons")</small></label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUKMPG" checked="@Model.UserConfig.UseUKMPG">
<label class="form-check-label" for="useUKMPG">@translator.Translate(userLanguage, "Use UK MPG Calculation")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation")</small></label>
</div>
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUnitForFuelCost" checked="@Model.UserConfig.UseUnitForFuelCost">
<label class="form-check-label" for="useUnitForFuelCost">@translator.Translate(userLanguage, "Default to Fuel Unit Cost")</label>
</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption">
<label class="form-check-label" for="useThreeDecimalGasConsumption">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Consumption")</label>
</div>
<hr />
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoReminderRefresh" checked="@Model.UserConfig.EnableAutoReminderRefresh">
<label class="form-check-label" for="enableAutoReminderRefresh">@translator.Translate(userLanguage, "Auto Refresh Lapsed Recurring Reminders")</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoOdometerInsert" checked="@Model.UserConfig.EnableAutoOdometerInsert">
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")</small></label>
</div>
<hr />
<div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideSoldVehicles" checked="@Model.UserConfig.HideSoldVehicles">
<label class="form-check-label" for="hideSoldVehicles">@translator.Translate(userLanguage, "Hide Sold Vehicles")</label>
</div>
<div class="form-check form-switch form-check-inline @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
</div>
<div class="form-check form-switch form-check-inline">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="showCalendar" checked="@Model.UserConfig.ShowCalendar">
<label class="form-check-label" for="showCalendar">@translator.Translate(userLanguage, "Show Calendar")</label>
</div>
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<hr />
<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">
<label class="form-check-label" for="enableAuth">@translator.Translate(userLanguage, "Enable Authentication")</label>
</div>
}
</div>
<div class="col-12 col-md-6">
<div class="row" id="visibleTabs">
<div class="col-12">
<div class="row">
<div class="col-11">
<span class="lead">@translator.Translate(userLanguage, "Visible Tabs")</span>
</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">
<ul class="list-group">
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ServiceRecord" id="serviceRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.ServiceRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="serviceRecordTab">@translator.Translate(userLanguage, "Service Records")</label>
</li>
<li class="list-group-item d-none">
<input onChange="updateSettings()" disabled class="form-check-input me-1" type="checkbox" value="Dashboard" id="dashboardTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.Dashboard) ? "checked" : "")>
<label class="form-check-label stretched-link" for="dashboardTab">@translator.Translate(userLanguage, "Dashboard")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="RepairRecord" id="repairRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.RepairRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="repairRecordTab">@translator.Translate(userLanguage, "Repairs")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="UpgradeRecord" id="upgradeRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.UpgradeRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="upgradeRecordTab">@translator.Translate(userLanguage, "Upgrades")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="GasRecord" id="gasRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.GasRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="gasRecordTab">@translator.Translate(userLanguage, "Fuel")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="OdometerRecord" id="odometerRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.OdometerRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="odometerRecordTab">@translator.Translate(userLanguage, "Odometer")</label>
</li>
</ul>
</div>
<div class="col-12 col-md-6">
<ul class="list-group">
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="TaxRecord" id="taxRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.TaxRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="taxRecordTab">@translator.Translate(userLanguage, "Taxes")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="NoteRecord" id="noteRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.NoteRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="noteRecordTab">@translator.Translate(userLanguage, "Notes")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ReminderRecord" id="reminderRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.ReminderRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="reminderRecordTab">@translator.Translate(userLanguage, "Reminder")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="SupplyRecord" id="supplyRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.SupplyRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="supplyRecordTab">@translator.Translate(userLanguage, "Supplies")</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="PlanRecord" id="planRecordTab" @(Model.UserConfig.VisibleTabs.Contains(ImportMode.PlanRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="planRecordTab">@translator.Translate(userLanguage, "Planner")</label>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<span class="lead">@translator.Translate(userLanguage, "Default Tab")</span>
<select class="form-select" onchange="updateSettings()" id="defaultTab">
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.Dashboard)) value="Dashboard">@translator.Translate(userLanguage, "Dashboard")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.ServiceRecord)) value="ServiceRecord">@translator.Translate(userLanguage, "Service Record")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.RepairRecord)) value="RepairRecord">@translator.Translate(userLanguage, "Repairs")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.UpgradeRecord)) value="UpgradeRecord">@translator.Translate(userLanguage, "Upgrades")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.GasRecord)) value="GasRecord">@translator.Translate(userLanguage, "Fuel")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.TaxRecord)) value="TaxRecord">@translator.Translate(userLanguage, "Tax")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.NoteRecord)) value="NoteRecord">@translator.Translate(userLanguage, "Notes")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.ReminderRecord)) value="ReminderRecord">@translator.Translate(userLanguage, "Reminders")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.SupplyRecord)) value="SupplyRecord">@translator.Translate(userLanguage, "Supplies")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.PlanRecord)) value="PlanRecord">@translator.Translate(userLanguage, "Planner")</!option>
<!option @(StaticHelper.DefaultTabSelected(Model.UserConfig, ImportMode.OdometerRecord)) value="OdometerRecord">@translator.Translate(userLanguage, "Odometer")</!option>
</select>
</div>
<div class="col-12 col-md-6">
<span class="lead">@translator.Translate(userLanguage, "Language")</span>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="input-group">
<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 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>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="row">
<div class="col-12 col-md-6">
<span class="lead">@translator.Translate(userLanguage, "Backups")</span>
<div class="row">
<div class="col-6 d-grid">
<button onclick="makeBackup()" class="btn btn-primary btn-md text-truncate">@translator.Translate(userLanguage, "Create")</button>
</div>
<div class="col-6 d-grid">
<input onChange="restoreBackup(this)" type="file" accept=".zip" class="d-none" id="inputBackup">
<button onclick="openRestoreBackup()" class="btn btn-secondary btn-md text-truncate">@translator.Translate(userLanguage, "Restore")</button>
</div>
</div>
</div>
<div class="col-12 col-md-6">
<span class="lead">@translator.Translate(userLanguage, "Manage Languages")</span>
<div class="row">
<div class="col-6 d-grid">
<input onChange="uploadLanguage(this)" type="file" accept=".json" class="d-none" id="inputLanguage">
<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 class="col-6 d-grid">
<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 class="row">
<div class="col-12">
<div class="row">
<div class="col-12">
<span class="lead text-wrap">@translator.Translate(userLanguage, "Server-wide Settings")</span>
</div>
</div>
<div class="row">
<div class="col-6 d-grid">
<button onclick="showExtraFieldModal()" class="btn btn-primary btn-md text-truncate">@translator.Translate(userLanguage, "Extra Fields")</button>
</div>
<div class="col-6 d-grid">
<a href="/setup" class="btn btn-secondary btn-md text-truncate">@translator.Translate(userLanguage, "Configure")</a>
</div>
</div>
</div>
</div>
}
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center">
<h6 class="display-6 mt-2">@translator.Translate(userLanguage, "About")</h6>
</div>
<hr />
<div class="col-12 col-md-6">
<div class="d-flex justify-content-center">
<img src="/defaults/motovaultpro_logo.png" />
</div>
<div class="d-flex justify-content-center">
<small class="text-body-secondary">@($"{translator.Translate(userLanguage, "Version")} {StaticHelper.VersionNumber}")</small>
</div>
<p class="lead">
Developed in Madison, Wisconsin by FB Technologies LLC.
</p>
<p class="lead">
If you enjoyed using this app, please consider posting and building the community.<br />
</p>
<p class="lead">
This was forked from https://github.com/hargata/lubelog/<br />
</p>
</div>
<div class="col-12 col-md-6">
<div class="d-flex justify-content-center">
<h6 class="display-7 mt-2">Open Source Dependencies</h6>
</div>
<p class="lead">
MotoVaultPro utilizes open-source dependencies to serve you the best possible user experience, those dependencies are:
</p>
<ul class="list-group">
<li class="list-group-item">Bootstrap</li>
<li class="list-group-item">Bootstrap-DatePicker</li>
<li class="list-group-item">LiteDB</li>
<li class="list-group-item">Npgsql</li>
<li class="list-group-item">SweetAlert2</li>
<li class="list-group-item">CsvHelper</li>
<li class="list-group-item">Chart.js</li>
<li class="list-group-item">Drawdown</li>
<li class="list-group-item">MailKit</li>
<li class="list-group-item">Masonry</li>
</ul>
</div>
</div>
<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-dialog modal-lg" role="document">
<div class="modal-content" id="extraFieldModalContent">
</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 motovaultpro-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>
function enableAuthCheckChanged() {
var enableAuth = $("#enableAuth").is(":checked");
if (enableAuth) {
//swal dialog to set up username and password.
Swal.fire({
title: 'Setup Credentials',
html: `
<input type="text" id="authUsername" class="swal2-input" placeholder="Username">
<input type="password" id="authPassword" class="swal2-input" placeholder="Password" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Setup',
focusConfirm: false,
preConfirm: () => {
const username = $("#authUsername").val();
const password = $("#authPassword").val();
if (!username || !password) {
Swal.showValidationMessage(`Please enter username and password`)
}
return { username, password }
},
}).then(function (result) {
if (result.isConfirmed) {
$.post('/Login/CreateLoginCreds', { userName: result.value.username, password: result.value.password }, function (data) {
if (data) {
setTimeout(function () { window.location.href = '/Login' }, 500);
} else {
errorToast(genericErrorMessage());
}
})
}
});
} else {
$.post('/Login/DestroyLoginCreds', function (data) {
if (data) {
setTimeout(function () { window.location.href = '/Home' }, 1000);
} else {
errorToast(genericErrorMessage());
}
});
}
}
loadSponsors();
</script>

View File

@@ -0,0 +1,70 @@
@using MotoVaultPro.Helper
@model Sponsors
@inject ITranslationHelper translator
@inject IConfigHelper config
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="d-flex justify-content-center">
<h6 class="display-6 mt-2">@translator.Translate(userLanguage, "Sponsors")</h6>
</div>
<hr />
<div class="col-12">
<div class="d-flex justify-content-center">
<p><a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" href="https://docs.motovaultpro.com/Misc/Funding" target="_blank">Become a Sponsor</a></p>
</div>
</div>
@if (Model.LifeTime.Any())
{
<div class="col-12">
<div class="d-flex justify-content-center">
<h6 class="display-7 mt-2">Lifetime</h6>
</div>
<div class="d-flex justify-content-center">
<p class="lead">
@string.Join(", ", Model.LifeTime)
</p>
</div>
</div>
}
@if (Model.Gold.Any())
{
<div class="col-12">
<div class="d-flex justify-content-center">
<h6 class="display-7 mt-2">Gold</h6>
</div>
<div class="d-flex justify-content-center">
<p class="lead">
@string.Join(", ", Model.Gold)
</p>
</div>
</div>
}
@if (Model.Silver.Any())
{
<div class="col-12">
<div class="d-flex justify-content-center">
<h6 class="display-7 mt-2">Silver</h6>
</div>
<div class="d-flex justify-content-center">
<p class="lead">
@string.Join(", ", Model.Silver)
</p>
</div>
</div>
}
@if (Model.Bronze.Any())
{
<div class="col-12">
<div class="d-flex justify-content-center">
<h6 class="display-7 mt-2">Bronze</h6>
</div>
<div class="d-flex justify-content-center">
<p class="lead">
@string.Join(", ", Model.Bronze)
</p>
</div>
</div>
}

View File

@@ -0,0 +1,48 @@
@using MotoVaultPro.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["MOTOVAULTPRO_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>

View File

@@ -0,0 +1,76 @@
@using MotoVaultPro.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>

View File

@@ -0,0 +1,10 @@
@model List<ExtraField>
@if (Model.Any())
{
<ul class='list-group list-group-flush'>
@foreach (ExtraField field in Model)
{
<li><b>@field.Name</b> : @field.Value</li>
}
</ul>
}

View File

@@ -0,0 +1,26 @@
@inject IConfigHelper config
@inject ITranslationHelper translator
@using MotoVaultPro.Helper
@model List<Vehicle>
@{
var userLanguage = config.GetUserConfig(User).UserLanguage;
}
@if (Model.Any())
{
<div id="vehicleSelector">
<ul class="list-group">
@foreach (Vehicle vehicle in Model)
{
<li class="list-group-item text-start">
<input class="form-check-input" type="checkbox" value="@vehicle.Id" id="vehicleCheck_@vehicle.Id">
<label class="form-check-label stretched-link" for="vehicleCheck_@vehicle.Id">@($"{vehicle.Year} {vehicle.Make} {vehicle.Model} ({StaticHelper.GetVehicleIdentifier(vehicle)})")</label>
</li>
}
</ul>
</div>
} else
{
<div id="vehicleSelector"><span class="lead">@translator.Translate(userLanguage, "No Vehicles Available")</span></div>
}

View File

@@ -0,0 +1,28 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model string
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title" id="widgetEditorModalLabel">@translator.Translate(userLanguage, "Custom Widgets Editor")</h5>
<button type="button" class="btn-close" onclick="hideCustomWidgets()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form class="form-inline">
<div class="form-group" style="height:50vh; overflow-x:hidden; overflow-y:auto;">
<div class="row">
<div class="col-12" style="height:48vh;">
<textarea id="widgetEditor" style="width:100%; height:100%;">@Model</textarea>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger me-auto" onclick="deleteCustomWidgets()">@translator.Translate(userLanguage, "Delete")</button>
<button type="button" class="btn btn-secondary" onclick="hideCustomWidgets()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="saveCustomWidgets()">@translator.Translate(userLanguage, "Save")</button>
</div>

View File

@@ -0,0 +1,32 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
}
@{
ViewData["Title"] = "Forgot Password";
}
@section Scripts {
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="no-top-pad container d-flex align-items-center justify-content-center" style="height:100vh">
<div>
<div style="max-width:204px;">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
<input type="text" onkeyup="callBackOnEnter(event, requestPasswordReset)" id="inputUserName" class="form-control">
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="requestPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Request")</button>
</div>
<div class="d-grid">
<a href="/Login/ResetPassword" class="btn btn-link mt-2">@translator.Translate(userLanguage, "I Have a Token")</a>
</div>
<div class="d-grid">
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
</div>
</div>
</div>
</div>

67
Views/Login/Index.cshtml Normal file
View File

@@ -0,0 +1,67 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model string
@{
var userLanguage = config.GetServerLanguage();
var registrationDisabled = config.GetServerDisabledRegistration();
var openIdConfigName = config.GetOpenIDConfig().Name;
}
@{
ViewData["Title"] = "Login";
}
@section Scripts {
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="no-top-pad container d-flex align-items-center justify-content-center" style="height:100vh">
<div>
<div class="text-center" style="max-width:204px;">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
<input type="text" id="inputUserName" class="form-control">
</div>
<div class="form-group">
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
<div class="input-group">
<input type="password" id="inputUserPassword" onkeyup="callBackOnEnter(event, performLogin)" class="form-control">
<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 class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputPersistent">
<label class="form-check-label" for="inputPersistent">@translator.Translate(userLanguage, "Remember Me")</label>
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Login")</button>
</div>
@if (!string.IsNullOrWhiteSpace(openIdConfigName))
{
<div class="d-grid">
<button type="button" class="btn btn-secondary mt-2" onclick="remoteLogin()"><i class="bi bi-box-arrow-in-right me-2"></i>@($"{translator.Translate(userLanguage, "Login via")} {openIdConfigName}")</button>
</div>
}
<div class="d-flex justify-content-center">
<div class="form-group">
<small class="text-body-secondary">@config.GetMOTD()</small>
</div>
</div>
<div class="d-grid">
<a href="/Login/ForgotPassword" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Forgot Password")</a>
</div>
@if (!registrationDisabled)
{
<div class="d-grid">
<a href="/Login/Registration" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Register")</a>
</div>
}
</div>
</div>
</div>
<script>
function getRedirectURL() {
return { url: decodeHTMLEntities('@Model') };
}
</script>

View File

@@ -0,0 +1,72 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
var openRegistrationEnabled = config.GetServerOpenRegistration();
}
@model string
@{
ViewData["Title"] = "Register";
}
@section Scripts {
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="no-top-pad container d-flex align-items-center justify-content-center" style="height:100vh">
<div>
<div style="max-width:204px;">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
<div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
@if (openRegistrationEnabled)
{
<div class="input-group">
<input type="text" id="inputToken" class="form-control">
<div class="input-group-text">
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendOpenIdRegistrationToken()"><i class="bi bi-send"></i></button>
</div>
</div>
}
else
{
<input type="text" id="inputToken" class="form-control">
}
</div>
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
<input type="text" id="inputUserName" class="form-control" value="@Model" onkeyup="callBackOnEnter(event, performOpenIdRegistration)">
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performOpenIdRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Register")</button>
</div>
<div class="d-grid">
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
</div>
</div>
</div>
</div>
<script>
function performOpenIdRegistration() {
var token = $("#inputToken").val();
var userName = $("#inputUserName").val();
var userEmail = decodeHTMLEntities('@Model');
$.post('/Login/RegisterOpenIdUser', { userName: userName, token: token, emailAddress: userEmail }, function (data) {
if (data.success) {
successToast(data.message);
setTimeout(function () { window.location.href = '/Home' }, 500);
} else {
errorToast(data.message);
}
});
}
function sendOpenIdRegistrationToken(){
var userEmail = decodeHTMLEntities('@Model');
$.post('/Login/SendRegistrationToken', { emailAddress: userEmail }, function (data) {
if (data.success) {
successToast(data.message);
} else {
errorToast(data.message);
}
});
}
</script>

View File

@@ -0,0 +1,57 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
var openRegistrationEnabled = config.GetServerOpenRegistration();
}
@model LoginModel
@{
ViewData["Title"] = "Register";
}
@section Scripts {
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="no-top-pad container d-flex align-items-center justify-content-center" style="height:100vh">
<div>
<div style="max-width:204px;">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
<div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
@if (openRegistrationEnabled) {
<div class="input-group">
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
<div class="input-group-text">
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="sendRegistrationToken()"><i class="bi bi-send"></i></button>
</div>
</div>
} else {
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
}
</div>
<div class="form-group">
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
<input type="text" id="inputEmail" class="form-control" value="@Model.EmailAddress">
</div>
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
<input type="text" id="inputUserName" class="form-control">
</div>
<div class="form-group">
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
<div class="input-group">
<input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performRegistration)">
<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 class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performRegistration()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Register")</button>
</div>
<div class="d-grid">
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
@model List<OperationResponse>
@{
ViewData["Title"] = "Remote Auth Debug";
}
<div class="no-top-pad">
<div class="mt-2">
@foreach (OperationResponse result in Model)
{
<div class="row">
<div class="col-12">
<div class="alert text-wrap text-break @(result.Success ? "alert-success" : "alert-danger")" role="alert">
@result.Message
</div>
</div>
</div>
}
</div>
</div>

View File

@@ -0,0 +1,43 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
}
@model LoginModel
@{
ViewData["Title"] = "Reset Password";
}
@section Scripts {
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="no-top-pad container d-flex align-items-center justify-content-center" style="height:100vh">
<div>
<div style="max-width:204px;">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
<div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
<input type="text" id="inputToken" class="form-control" value="@Model.Token">
</div>
<div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label>
<input type="text" id="inputEmail" class="form-control" value="@Model.EmailAddress">
</div>
<div class="form-group">
<label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label>
<div class="input-group">
<input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performPasswordReset)">
<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 class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="performPasswordReset()"><i class="bi bi-box-arrow-in-right me-2"></i>@translator.Translate(userLanguage, "Reset Password")</button>
</div>
<div class="d-grid">
<a href="/Login/Index" class="btn btn-link mt-2">@translator.Translate(userLanguage, "Back to Login")</a>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,92 @@
@{
ViewData["Title"] = "Database Migration";
}
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userLanguage = config.GetServerLanguage();
}
@using MotoVaultPro.Helper
@section Nav {
<div class="container-fluid motovaultpro-navbar-container frosted hideOnPrint">
<div class="row mt-2">
<div class="d-flex motovaultpro-navbar">
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
<img src="@config.GetSmallLogoUrl()" class="motovaultpro-logo" />
</div>
<span class="text-truncate lead">@translator.Translate(userLanguage, "Database Migration")</span>
</div>
</div>
<hr />
</div>
}
@model AdminViewModel
<div class="container">
<div class="row">
<div class="col-12">
<ul class="list-group list-group-flush">
<li class="list-group-item">@translator.Translate(userLanguage, "Instructions")</li>
<li class="list-group-item">@translator.Translate(userLanguage, "Use this tool to migrate data between LiteDB and Postgres")</li>
<li class="list-group-item">@translator.Translate(userLanguage, "Note that it is recommended that the Postgres DB is empty when importing from LiteDB to prevent primary key errors.")</li>
</ul>
</div>
</div>
<hr />
<div class="row d-flex justify-content-center">
<div class="col-3">
<div class="d-grid">
<input onChange="importPostgresData(this)" type="file" accept=".db" class="d-none" id="inputImport">
<button type="button" class="btn btn-warning mt-2" onclick="importToPostgres()"><i class="bi bi-upload me-2"></i>@translator.Translate(userLanguage, "Import To Postgres")</button>
</div>
<div class="d-grid">
<button type="button" class="btn btn-warning mt-2" onclick="exportFromPostgres()"><i class="bi bi-download me-2"></i>@translator.Translate(userLanguage, "Export From Postgres")</button>
</div>
</div>
</div>
</div>
<script>
function exportFromPostgres(){
sloader.show();
$.get('/Migration/Export', function (data) {
sloader.hide();
if (data.success) {
window.location.href = data.message;
} else {
errorToast(genericErrorMessage());
}
});
}
function importPostgresData(event) {
let formData = new FormData();
formData.append("file", event.files[0]);
sloader.show();
$.ajax({
url: "/Files/HandleFileUpload",
data: formData,
cache: false,
processData: false,
contentType: false,
type: 'POST',
success: function (response) {
if (response.trim() != '') {
$.post('/Migration/Import', { fileName: response }, function (data) {
sloader.hide();
if (data.success) {
successToast(data.message);
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
} else {
errorToast(genericErrorMessage());
}
});
}
},
error: function () {
sloader.hide();
errorToast("An error has occurred, please check the file size and try again later.");
}
});
}
function importToPostgres() {
$("#inputImport").trigger('click');
}
</script>

8
Views/Shared/401.cshtml Normal file
View File

@@ -0,0 +1,8 @@
<div class="row">
<div class="col-1">
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
</div>
<div class="col-11">
<h1>Access Denied</h1>
</div>
</div>

25
Views/Shared/Error.cshtml Normal file
View File

@@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

180
Views/Shared/_Layout.cshtml Normal file
View File

@@ -0,0 +1,180 @@
@using MotoVaultPro.Helper
@using System.Globalization
<!DOCTYPE html>
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var useDarkMode = userConfig.UseDarkMode;
var useSystemColorMode = userConfig.UseSystemColorMode;
var enableCsvImports = userConfig.EnableCsvImports;
var useMPG = userConfig.UseMPG;
var useMarkDown = userConfig.UseMarkDownOnSavedNotes;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var automaticDecimalFormat = userConfig.AutomaticDecimalFormat;
var shortDatePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
var firstDayOfWeek = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
var userLanguage = userConfig.UserLanguage;
shortDatePattern = shortDatePattern.ToLower();
if (!shortDatePattern.Contains("dd"))
{
shortDatePattern = shortDatePattern.Replace("d", "dd");
}
if (!shortDatePattern.Contains("mm"))
{
shortDatePattern = shortDatePattern.Replace("m", "mm");
}
}
<html lang="en" data-bs-theme="@(useDarkMode ? "dark" : "light")">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-title" content="MotoVaultPro" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#181818">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#f3f3f3">
<title>@ViewData["Title"] - MotoVaultPro</title>
<link rel="icon" type="image/x-icon" href="~/favicon.png">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap-icons.css" />
<link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" />
<link rel="stylesheet" href="~/lib/bootstrap-tagsinput/bootstrap-tagsinput.css" />
<link rel="stylesheet" href="~/css/site.css"/>
<link rel="stylesheet" href="~/css/loader.css"/>
<link rel="stylesheet" href="~/sweetalert/sweetalert2.min.css"/>
<link rel="icon" sizes="192x192" href="~/defaults/motovaultpro_icon_192.png" />
<link rel="icon" sizes="128x128" href="~/defaults/motovaultpro_icon_128.png" />
<link rel="apple-touch-icon" sizes="128x128" href="~/defaults/motovaultpro_icon_128.png" />
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="~/defaults/motovaultpro_icon_128.png" />
<link rel="apple-touch-startup-image" href="~/defaults/motovaultpro_launch.png" />
<link rel="manifest" href="~/manifest.json">
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/shared.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
<script src="~/lib/bootstrap-tagsinput/bootstrap-tagsinput.js"></script>
<script src="~/sweetalert/sweetalert2.all.min.js"></script>
<script src="~/js/loader.js?v=@StaticHelper.VersionNumber"></script>
<script>
function getGlobalConfig() {
return {
useDarkMode: "@useDarkMode" == "True" || ("@useSystemColorMode" == "True" && window.matchMedia('(prefers-color-scheme: dark)').matches),
enableCsvImport : "@enableCsvImports" == "True",
useMarkDown: "@useMarkDown" == "True",
currencySymbol: decodeHTMLEntities("@numberFormat.CurrencySymbol"),
useThreeDecimals: "@useThreeDecimals" == "True",
useMPG: "@useMPG" == "True",
firstDayOfWeek: @firstDayOfWeek
}
}
function getShortDatePattern() {
return {
pattern: "@shortDatePattern"
}
}
function globalParseFloat(input){
//remove thousands separator.
var thousandSeparator = decodeHTMLEntities("@numberFormat.NumberGroupSeparator");
var decimalSeparator = decodeHTMLEntities("@numberFormat.NumberDecimalSeparator");
var currencySymbol = decodeHTMLEntities("@numberFormat.CurrencySymbol");
if (input == "---") {
input = "0";
}
//strip thousands from input.
input = input.replace(thousandSeparator, "");
//convert to JS format where decimal is only separated by .
input = input.replace(decimalSeparator, ".");
//remove any currency symbol
input = input.replace(currencySymbol, "");
return parseFloat(input);
}
function globalFloatToString(input) {
var decimalSeparator = decodeHTMLEntities("@numberFormat.NumberDecimalSeparator");
input = input.replace(".", decimalSeparator);
return input;
}
function fixDecimalInput(input, numOfDecimals) {
if ("@automaticDecimalFormat" == "True") {
//get the decimal separator.
var decimalSeparator = decodeHTMLEntities("@numberFormat.NumberDecimalSeparator");
var inputText = $(input).val().trim();
inputText = inputText.replace(decimalSeparator, '');
inputText = +inputText;
if (isNaN(inputText)){
return;
}
inputText = inputText.toString();
if (inputText == '') {
return;
};
//check number of decimals.
if (inputText.length <= numOfDecimals) {
//less than number of decimals, assume everything is behind the decimal.
inputText = `0${decimalSeparator}${inputText}`; //add leading zero.
$(input).val(inputText);
} else {
//check if leading zero
var charToSlice = numOfDecimals * -1;
var charsBehindDecimal = inputText.slice(charToSlice);
var charsFrontDecimal = inputText.substr(0, inputText.length - numOfDecimals);
inputText = `${charsFrontDecimal}${decimalSeparator}${charsBehindDecimal}`;
$(input).val(inputText);
}
}
}
function interceptDecimalKeys(event) {
if ("@automaticDecimalFormat" == "True") {
if (event.which == 190 || event.which == 188) {
event.preventDefault(); //intercept keys.
}
}
}
function genericErrorMessage(){
return decodeHTMLEntities('@translator.Translate(userLanguage, "An error has occurred, please try again later")');
}
function globalAppendCurrency(input){
//check currency symbol position
var currencySymbolPosition = "@numberFormat.CurrencyPositivePattern";
var currencySymbol = decodeHTMLEntities("@numberFormat.CurrencySymbol");
switch (currencySymbolPosition) {
case "0":
return `${currencySymbol}${input}`;
break;
case "1":
return `${input}${currencySymbol}`;
break;
case "2":
return `${currencySymbol} ${input}`;
break;
case "3":
return `${input} ${currencySymbol}`;
break;
}
}
function setThemeBasedOnDevice() {
var systemPrefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (systemPrefersDarkMode) {
$(document.documentElement).attr("data-bs-theme", "dark");
}
}
</script>
@await RenderSectionAsync("Scripts", required: false)
</head>
<body>
@await RenderSectionAsync("Nav", required: false)
<div class="container motovaultpro-body-container">
<main role="main">
@RenderBody()
</main>
</div>
@await RenderSectionAsync("Footer", required: false)
</body>
</html>
@if (useSystemColorMode)
{
<script>
setThemeBasedOnDevice();
</script>
}

View File

@@ -0,0 +1,48 @@
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}

View File

@@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

View File

@@ -0,0 +1,116 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model CollisionRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Repair Record") : translator.Translate(userLanguage, "Edit Repair Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditCollisionRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddCollisionRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="collisionRecordDate">@translator.Translate(userLanguage, "Date")</label>
<div class="input-group">
<input type="text" id="collisionRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date repair was performed")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="collisionRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="collisionRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when repaired")" value="@(isNew || Model.Mileage == default ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('collisionRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="collisionRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) repaired(i.e. Alternator)")" value="@Model.Description">
@if (isNew)
{
<div class="row">
<div class="col-12">
<a onclick="showRecurringReminderSelector('collisionRecordDescription', 'collisionRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
</div>
</div>
}
<label for="collisionRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
@await Html.PartialAsync("Supply/_SupplyStore", new SupplyStore { Tab = "RepairRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="collisionRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="collisionRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="collisionRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="collisionRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (isNew)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
<label class="form-check-label" for="addReminderCheck">
@translator.Translate(userLanguage, "Add Reminder")
</label>
</div>
}
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteCollisionRecord(@Model.Id)">@translator.Translate(userLanguage, "Delete")</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'ServiceRecord')">@translator.Translate(userLanguage, "Service Records")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'UpgradeRecord')">@translator.Translate(userLanguage, "Upgrades")</a></li>
</ul>
</div>
}
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle()">@translator.Translate(userLanguage, "Add New Repair Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Repair Record")</button>
}
</div>
@await Html.PartialAsync("Supply/_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "collisionRecordCost" })
<script>
var selectedSupplies = [];
var copySuppliesAttachments = false;
var recurringReminderRecordId = [];
function getCollisionRecordModelData() {
return { id: @Model.Id}
}
</script>

View File

@@ -0,0 +1,207 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var userLanguage = userConfig.UserLanguage;
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.RepairRecord);
}
@model List<CollisionRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Repair Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('accident-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddCollisionRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Repair Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('RepairRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('accident-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Date" checked>
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Odometer" checked>
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Cost" checked>
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'RepairRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddCollisionRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Repair Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (CollisionRecord collisionRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@collisionRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditCollisionRecordModal,@collisionRecord.Id)" data-tags='@string.Join(" ", collisionRecord.Tags)'>
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(collisionRecord.Date)">@collisionRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(collisionRecord.Mileage == default ? "---" : collisionRecord.Mileage.ToString())</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@collisionRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(collisionRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", collisionRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = collisionRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="collisionRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="collisionRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Service Records")</span><i class="bi bi-card-checklist"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Upgrades")</span><i class="bi bi-wrench-adjustable"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,292 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject IGasHelper gasHelper
@inject ITranslationHelper translator
@model GasRecordViewModelContainer
@{
var userConfig = config.GetUserConfig(User);
var enableCsvImports = userConfig.EnableCsvImports;
var useMPG = userConfig.UseMPG;
var useUKMPG = userConfig.UseUKMPG;
var hideZero = userConfig.HideZero;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
var gasCostFormat = useThreeDecimals ? "C3" : "C2";
var gasConsumptionFormat = useThreeDecimalsConsumption ? "F3" : "F2";
var userLanguage = userConfig.UserLanguage;
var useKwh = Model.UseKwh;
var useHours = Model.UseHours;
var recordTags = Model.GasRecords.SelectMany(x => x.Tags).Distinct();
string preferredFuelEconomyUnit = userConfig.PreferredGasMileageUnit;
string preferredGasUnit = userConfig.PreferredGasUnit;
string consumptionUnit;
string fuelEconomyUnit;
string distanceUnit = useHours ? "h" : (useMPG ? "mi." : "km");
if (useKwh)
{
consumptionUnit = "kWh";
fuelEconomyUnit = useMPG ? $"{distanceUnit}/kWh" : $"kWh/100{distanceUnit}";
}
else if (useMPG && useUKMPG)
{
consumptionUnit = "imp gal";
fuelEconomyUnit = useHours ? "h/g" : "mpg";
}
else if (useUKMPG)
{
fuelEconomyUnit = useHours ? "l/100h" : "l/100mi.";
consumptionUnit = "l";
distanceUnit = useHours ? "h" : "mi.";
}
else
{
consumptionUnit = useMPG ? "US gal" : "l";
fuelEconomyUnit = useHours ? (useMPG ? "h/g" : "l/100h") : (useMPG ? "mpg" : "l/100km");
}
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.GasRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.GasRecord);
}
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Gas Records")}: {Model.GasRecords.Count()}")</span>
@if (Model.GasRecords.Where(x => x.MilesPerGallon > 0).Any())
{
<span class="ms-2 badge bg-primary" id="averageFuelMileageLabel">@($"{translator.Translate(userLanguage, "Average Fuel Economy")}: {gasHelper.GetAverageGasMileage(Model.GasRecords, useMPG)}")</span>
if (useMPG)
{
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage, "Min Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage, "Max Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
}
else
{
<span class="ms-2 badge bg-primary" id="minFuelMileageLabel">@($"{translator.Translate(userLanguage, "Min Fuel Economy")}: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
<span class="ms-2 badge bg-primary" id="maxFuelMileageLabel">@($"{translator.Translate(userLanguage, "Max Fuel Economy")}: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
}
<span class="ms-2 badge bg-success" id="totalDistanceLabel">@($"{translator.Translate(userLanguage, "Total Distance")}: {Model.GasRecords.Sum(x => x.DeltaMileage).ToString() ?? "0"} {distanceUnit}")</span>
}
<span class="ms-2 badge bg-success" id="totalFuelConsumedLabel">@($"{translator.Translate(userLanguage, "Total Fuel Consumed")}: {Model.GasRecords.Sum(x => x.Gallons).ToString("F")}")</span>
<span class="ms-2 badge bg-success" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total Cost")}: {Model.GasRecords.Sum(x => x.Cost).ToString(gasCostFormat)}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="toggleGasFilter(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Gas Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchGasTableRows()">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='daterefueled' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_DateRefueled" checked>
<label class="form-check-label stretched-link" for="chkCol_DateRefueled">@translator.Translate(userLanguage, "Date Refueled")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Odometer" checked>
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='delta' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Delta" checked>
<label class="form-check-label stretched-link" for="chkCol_Delta">@translator.Translate(userLanguage, "Delta")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='consumption' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Consumption" checked>
<label class="form-check-label stretched-link" for="chkCol_Consumption">@translator.Translate(userLanguage, "Consumption")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='fueleconomy' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_FuelEconomy" checked>
<label class="form-check-label stretched-link" for="chkCol_FuelEconomy">@translator.Translate(userLanguage, "Fuel Economy")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Cost" checked>
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='unitcost' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_UnitCost" checked>
<label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="chkCol_Notes">
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'GasRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Gas Record")</button>
}
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 flex-grow-1 text-truncate" data-column="daterefueled">@translator.Translate(userLanguage, "Date Refueled")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@($"{translator.Translate(userLanguage, "Odometer")}({distanceUnit})")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="delta" style="cursor:pointer;" onclick="toggleSort('gas-tab-pane', this)">@($"Δ({distanceUnit})")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="consumption" data-gas="consumption" data-unit="@consumptionUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{translator.Translate(userLanguage, "Consumption")}({consumptionUnit})")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas="fueleconomy" data-unit="@fuelEconomyUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{@translator.Translate(userLanguage, "Fuel Economy")}({fuelEconomyUnit})")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Unit Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (GasRecordViewModel gasRecord in Model.GasRecords)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@gasRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditGasRecordModal,@gasRecord.Id)" data-tags='@string.Join(" ", gasRecord.Tags)'>
<td class="col-2 flex-grow-1 text-truncate" data-column="daterefueled">@gasRecord.Date</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@(gasRecord.Mileage == default ? "---" : gasRecord.Mileage.ToString())</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="delta" data-gas-type="totaldistance">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString(gasConsumptionFormat)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="fueleconomy" data-gas-type="fueleconomy" data-aggregated='@(gasRecord.IncludeInAverage.ToString().ToLower())'>@(gasRecord.MilesPerGallon == 0 ? "---" : gasRecord.MilesPerGallon.ToString("F"))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="unitcost" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", gasRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = gasRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="gasRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="gasRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}
<script>
@if (!string.IsNullOrWhiteSpace(preferredFuelEconomyUnit))
{
@:convertFuelMileageUnits(decodeHTMLEntities('@fuelEconomyUnit'), decodeHTMLEntities('@preferredFuelEconomyUnit'), false);
}
@if (!string.IsNullOrWhiteSpace(preferredGasUnit))
{
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
}
function getGasModelData(){
return {
distanceUnit: decodeHTMLEntities('@distanceUnit'),
consumptionUnit: decodeHTMLEntities('@consumptionUnit'),
gasCostFormat: decodeHTMLEntities('@gasCostFormat'),
gasConsumptionFormat: decodeHTMLEntities('@gasConsumptionFormat')
}
}
</script>

View File

@@ -0,0 +1,134 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model GasRecordInputContainer
@{
var userConfig = config.GetUserConfig(User);
var useMPG = userConfig.UseMPG;
var useUKMPG = userConfig.UseUKMPG;
var userLanguage = userConfig.UserLanguage;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
var useKwh = Model.UseKwh;
var useHours = Model.UseHours;
var isNew = Model.GasRecord.Id == 0;
var useUnitFuelCost = userConfig.UseUnitForFuelCost;
string consumptionUnit;
string distanceUnit;
if (useKwh)
{
consumptionUnit = "kWh";
} else if (useUKMPG)
{
consumptionUnit = @translator.Translate(userLanguage, "liters");
}
else
{
consumptionUnit = useMPG ? @translator.Translate(userLanguage, "gallons") : @translator.Translate(userLanguage, "liters");
}
if (useHours)
{
distanceUnit = @translator.Translate(userLanguage, "hours");
}
else if (useUKMPG)
{
distanceUnit = @translator.Translate(userLanguage, "miles");
}
else
{
distanceUnit = useMPG ? @translator.Translate(userLanguage, "miles") : @translator.Translate(userLanguage, "kilometers");
}
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Gas Record") : translator.Translate(userLanguage, "Edit Gas Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditGasRecordModal({Model.GasRecord.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddGasRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="gasRecordDate">@translator.Translate(userLanguage,"Date")</label>
<div class="input-group">
<input type="text" id="gasRecordDate" placeholder="@translator.Translate(userLanguage,"Date refueled")" class="form-control" value="@Model.GasRecord.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="gasRecordMileage">@($"{translator.Translate(userLanguage,"Odometer Reading")}({distanceUnit})")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when refueled")" value="@(isNew || Model.GasRecord.Mileage == default ? "" : Model.GasRecord.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('gasRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="gasRecordGallons">@($"{translator.Translate(userLanguage, "Fuel Consumption")}({consumptionUnit})")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimalsConsumption ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
<label class="form-check-label" for="gasIsFillToFull">@translator.Translate(userLanguage,"Is Filled To Full")</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="gasIsMissed" checked="@Model.GasRecord.MissedFuelUp">
<label class="form-check-label" for="gasIsMissed">@translator.Translate(userLanguage,"Missed Fuel Up(Skip MPG Calculation)")</label>
</div>
<label for="GasRecordCost">@translator.Translate(userLanguage,"Cost")</label>
@if (isNew)
{
<div class="input-group">
<input type="text" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
<div class="input-group-text">
<select class="form-select form-select-sm" id="gasCostType">
<!option @(useUnitFuelCost ? "" : "selected") value="total">@translator.Translate(userLanguage,"Total")</!option>
<!option @(useUnitFuelCost ? "selected" : "") value="unit">@translator.Translate(userLanguage,"Unit")</!option>
</select>
</div>
</div>
} else
{
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
}
<label for="gasRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="gasRecordTag">
@foreach (string tag in Model.GasRecord.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.GasRecord.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="gasRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="gasRecordNotes" class="form-control" rows="5">@Model.GasRecord.Notes</textarea>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.GasRecord.Files)
@await Html.PartialAsync("_FileUploader", Model.GasRecord.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteGasRecord(@Model.GasRecord.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddGasRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle()">@translator.Translate(userLanguage,"Add New Gas Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveGasRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Gas Record")</button>
}
</div>
<script>
function getGasRecordModelData(){
return { id: @Model.GasRecord.Id}
}
</script>

View File

@@ -0,0 +1,53 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model GasRecordEditModel
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
var useThreeDecimalsConsumption = userConfig.UseThreeDecimalGasConsumption;
}
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage,"Edit Multiple Gas Records")</h5>
<button type="button" class="btn-close" onclick="hideAddGasRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<label for="gasRecordDate">@translator.Translate(userLanguage, "Date")</label>
<div class="input-group">
<input type="text" id="gasRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="gasRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<input type="number" inputmode="numeric" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="gasRecordConsumption">@translator.Translate(userLanguage, "Fuel Consumption")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimalsConsumption ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordConsumption" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="gasRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="@(useThreeDecimals ? "fixDecimalInput(this, 3)" : "fixDecimalInput(this, 2)")" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="gasRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
<select multiple class="form-select" id="gasRecordTag"></select>
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="gasRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="gasRecordNotes" class="form-control" rows="5"></textarea>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideAddGasRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="saveMultipleGasRecordsToVehicle()">@translator.Translate(userLanguage, "Edit")</button>
</div>
<script>
var recordsToEdit = [];
@foreach(int recordId in Model.RecordIds)
{
@:recordsToEdit.push(@recordId);
}
</script>

221
Views/Vehicle/Index.cshtml Normal file
View File

@@ -0,0 +1,221 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model Vehicle
@{
ViewData["Title"] = $"{Model.Year} {Model.Make} {Model.Model} ({StaticHelper.GetVehicleIdentifier(Model)})";
}
@section Scripts {
<script src="~/js/vehicle.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/servicerecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/gasrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/collisionrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/taxrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/reminderrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/upgraderecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/note.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/reports.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/planrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/odometerrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
@section Nav{
<div class="container-fluid motovaultpro-navbar-container frosted hideOnPrint">
<div class="row mt-2">
<div class="d-flex motovaultpro-navbar">
<div class="me-2" style="cursor:pointer;" onclick="returnToGarage()">
@if(userConfig.ShowVehicleThumbnail) {
<img src="@Model.ImageLocation" class="motovaultpro-vehicle-logo @(string.IsNullOrWhiteSpace(Model.SoldDate) ? "" : "sold")" />
} else {
<img src="@config.GetSmallLogoUrl()" class="motovaultpro-logo" />
}
</div>
<ul class="nav nav-tabs motovaultpro-tab flex-grow-1" id="vehicleTab" role="tablist">
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.Dashboard)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph"></i><span class="ms-2">@translator.Translate(userLanguage, "Dashboard")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.PlanRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-bar-chart-steps"></i><span class="ms-2">@translator.Translate(userLanguage, "Planner")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.OdometerRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-speedometer"></i><span class="ms-2">@translator.Translate(userLanguage, "Odometer")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ServiceRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist"></i><span class="ms-2">@translator.Translate(userLanguage, "Service Records")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.RepairRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon"></i><span class="ms-2">@translator.Translate(userLanguage, "Repairs")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.UpgradeRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-wrench-adjustable"></i><span class="ms-2">@translator.Translate(userLanguage, "Upgrades")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.GasRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump"></i><span class="ms-2">@translator.Translate(userLanguage, "Fuel")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.SupplyRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop"></i><span class="ms-2">@translator.Translate(userLanguage, "Supplies")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.TaxRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar"></i><span class="ms-2">@translator.Translate(userLanguage, "Taxes")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark"></i><span class="ms-2">@translator.Translate(userLanguage, "Notes")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
<button class="nav-link resizable-nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell"></i></div><span class="ms-2">@translator.Translate(userLanguage, "Reminders")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @(userConfig.TabOrder.Count + 1)">
<button class="nav-link resizable-nav-link" type="button" role="tab" aria-selected="false" onclick="showGlobalSearch()"><i class="bi bi-search"></i><span class="ms-2 ">@translator.Translate(userLanguage, "Search")</span></button>
</li>
<li class="nav-item dropdown nav-item-persist nav-item-more me-5" role="presentation" style="display:none; order: @(userConfig.TabOrder.Count + 2)">
<a class="nav-link resizable-nav-link" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-three-dots"></i></a>
<ul class="dropdown-menu">
</ul>
</li>
</ul>
<span style="cursor:pointer;" onclick="editVehicle(@Model.Id)" class="text-truncate"><span class="lead">@($"{Model.Year} {Model.Make} {Model.Model}")<small class="text-body-secondary">@($"(#{StaticHelper.GetVehicleIdentifier(Model)})")</small></span><span class="ms-2 motovaultpro-tab"><i class="bi bi-pencil-square"></i></span></span>
<div class="motovaultpro-navbar-button">
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list motovaultpro-menu-icon"></i></button>
</div>
</div>
</div>
<hr />
</div>
}
<div class="motovaultpro-mobile-nav" onclick="hideMobileNav()">
<ul class="nav navbar-nav" id="vehicleTab" role="tablist">
<li class="nav-item" role="presentation" style="order: -3">
<button class="nav-link" onclick="returnToGarage()"><span class="display-3 ms-2"><i class="bi bi-arrow-left-square me-2"></i>@translator.Translate(userLanguage,"Garage")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: -2">
<button class="nav-link" onclick="editVehicle(@Model.Id)"><span class="display-3 ms-2"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Edit Vehicle")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: -1">
<button class="nav-link" onclick="showGlobalSearch()"><span class="display-3 ms-2"><i class="bi bi-search me-2"></i>@translator.Translate(userLanguage, "Search")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.Dashboard)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>@translator.Translate(userLanguage, "Dashboard")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.PlanRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.PlanRecord)" id="plan-tab" data-bs-toggle="tab" data-bs-target="#plan-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-bar-chart-steps me-2"></i>@translator.Translate(userLanguage, "Planner")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.OdometerRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.OdometerRecord)" id="odometer-tab" data-bs-toggle="tab" data-bs-target="#odometer-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-speedometer me-2"></i>@translator.Translate(userLanguage, "Odometer")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ServiceRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>@translator.Translate(userLanguage, "Service Records")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.RepairRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-exclamation-octagon me-2"></i>@translator.Translate(userLanguage,"Repairs")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.UpgradeRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-wrench-adjustable me-2"></i>@translator.Translate(userLanguage, "Upgrades")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.GasRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-fuel-pump me-2"></i>@translator.Translate(userLanguage, "Fuel")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.SupplyRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.TaxRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-currency-dollar me-2"></i>@translator.Translate(userLanguage, "Taxes")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.NoteRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-journal-bookmark me-2"></i>@translator.Translate(userLanguage, "Notes")</span></button>
</li>
<li class="nav-item" role="presentation" style="order: @userConfig.TabOrder.FindIndex(x=>x == ImportMode.ReminderRecord)">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>@translator.Translate(userLanguage, "Reminders")</span></button>
</li>
</ul>
</div>
<div class="container">
<div class="tab-content" id="vehicleTabContent">
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.GasRecord)" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.TaxRecord)" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.NoteRecord)" id="notes-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.RepairRecord)" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ReminderRecord)" id="reminder-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.Dashboard)" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.SupplyRecord)" id="supply-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.PlanRecord)" id="plan-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.OdometerRecord)" id="odometer-tab-pane" role="tabpanel" tabindex="0"></div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="editVehicleModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="editVehicleModalContent">
</div>
</div>
</div>
<div class="modal fade" id="bulkImportModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content" id="bulkImportModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="reminderRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="reminderRecordModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="genericRecordEditModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="genericRecordEditModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="inputSuppliesModal" tabindex="-1" role="dialog" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="inputSuppliesModalContent"></div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="globalSearchModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="globalSearchModalContent">
<div class="modal-body">
<div class="input-group input-group-lg">
<input type="text" id="globalSearchInput" autocomplete="off" onkeyup="handleGlobalSearchKeyPress(event)" class="form-control" placeholder="@translator.Translate(userLanguage, "Search by Keyword")">
<button type="button" class="btn btn-outline-secondary" onclick="performGlobalSearch()"><i class="bi bi-search"></i></button>
</div>
<div class="form-check form-check-inline form-switch mt-1">
<input class="form-check-input" type="checkbox" role="switch" id="globalSearchAutoSearchCheck" checked>
<label class="form-check-label" for="globalSearchAutoSearchCheck">@translator.Translate(userLanguage, "Incremental Search")</label>
</div>
<div class="form-check form-check-inline form-switch mt-1">
<input class="form-check-input" type="checkbox" onChange="performGlobalSearch()" role="switch" id="globalSearchCaseSensitiveCheck" checked>
<label class="form-check-label" for="globalSearchCaseSensitiveCheck">@translator.Translate(userLanguage, "Case Sensitive")</label>
</div>
<div id="globalSearchModalResults"></div>
</div>
</div>
</div>
</div>
<div class="stickerPrintContainer hideOnPrint">
</div>
<script>
function GetVehicleId() {
return {
vehicleId: @Model.Id,
odometerOptional: @Model.OdometerOptional.ToString().ToLower(),
hasOdometerAdjustment: @Model.HasOdometerAdjustment.ToString().ToLower(),
useEngineHours: @Model.UseHours.ToString().ToLower(),
odometerDifference: decodeHTMLEntities('@Model.OdometerDifference'),
odometerMultiplier: decodeHTMLEntities('@Model.OdometerMultiplier')
};
}
function GetDefaultTab() {
return { tab: "@userConfig.DefaultTab" };
}
bindWindowResize();
checkRecurringTaxes();
checkNavBarOverflow();
</script>

View File

@@ -0,0 +1,71 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model Note
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Note") : translator.Translate(userLanguage, "Edit Note"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditNoteModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddNoteModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="noteIsPinned" checked="@Model.Pinned">
<label class="form-check-label" for="noteIsPinned">@translator.Translate(userLanguage,"Pinned")</label>
</div>
<label for="noteDescription">@translator.Translate(userLanguage,"Description")</label>
<input type="text" id="noteDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of the note")" value="@(isNew ? "" : Model.Description)">
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-12">
<label for="noteTextArea">@translator.Translate(userLanguage,"Notes")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea class="form-control vehicleNoteContainer" id="noteTextArea">@Model.NoteText</textarea>
</div>
<div class="col-12">
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
<div class="col-12">
<label for="noteRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="noteRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteNote(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddNoteModal()">@translator.Translate(userLanguage,"Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle()">@translator.Translate(userLanguage,"Add New Note")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle(true)">@translator.Translate(userLanguage,"Edit Note")</button>
}
</div>
<script>
function getNoteModelData(){
return { id: @Model.Id}
}
</script>

View File

@@ -0,0 +1,178 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<Note>
@{
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var enableCsvImports = userConfig.EnableCsvImports;
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.NoteRecord);
}
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Notes")}: {Model.Count()}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('notes-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Note")</button>
<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="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('notes-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('NoteRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'NoteRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('NoteRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'NoteRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('NoteRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'NoteRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('NoteRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'NoteRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Note")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-4 col-md-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-8 col-md-9 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Note")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (Note note in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@note.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditNoteModal,@note.Id)" data-tags='@string.Join(" ", note.Tags)'>
@if (note.Pinned)
{
<td class="col-4 col-md-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="description"><i class='bi bi-pin-fill me-2'></i>@note.Description</td>
} else
{
<td class="col-4 col-md-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@note.Description</td>
}
<td class="col-8 col-md-9 flex-grow-1 flex-shrink-1 text-truncate" data-record-type="cost" data-column="notes">@StaticHelper.TruncateStrings(note.NoteText, 100)</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", note.Files)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = note.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="noteModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog" role="document">
<div class="modal-content" id="noteModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, true)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Toggle Pin")</span><i class="bi bi-pin-angle-fill"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, false, true)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Pin")</span><i class="bi bi-pin-angle-fill"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, false, false)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Unpin")</span><i class="bi bi-pin-angle"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'NoteRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,121 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model OdometerRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Odometer Record") : translator.Translate(userLanguage, "Edit Odometer Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditOdometerRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddOdometerRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body d-none trip-modal">
<div class="row">
<div class="col-12">
<div class="alert alert-warning alert-dismissable" role="alert">
@translator.Translate(userLanguage, "Experimental Feature - Do not exit or minimize this app when recording. Verify all starting and ending odometers. Accuracy subject to hardware limitations.")
</div>
</div>
</div>
<div class="row">
<div class="col-12 d-flex justify-content-center">
<h1 class="display-6 text-body-secondary">@translator.Translate(userLanguage, "Current Odometer")</h1>
</div>
</div>
<div class="row">
<div class="col-12 d-flex justify-content-center align-items-center">
<h1 class="display-1 trip-odometer" onclick="toggleSubOdometer()"></h1>
<h1 class="display-6 trip-odometer-sub ms-2 d-none">0</h1>
</div>
</div>
</div>
<div class="modal-body odometer-modal" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="odometerRecordDate">@translator.Translate(userLanguage,"Date")</label>
<div class="input-group">
<input type="text" id="odometerRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date recorded")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="initialOdometerRecordMileage">@translator.Translate(userLanguage, "Initial Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="initialOdometerRecordMileage" @(Model.InitialMileage != default ? "disabled" : "") class="form-control" placeholder="@translator.Translate(userLanguage,"Initial Odometer reading")" value="@(Model.InitialMileage)">
@if (Model.InitialMileage != default)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-secondary zero-y-padding" onclick="toggleInitialOdometerEnabled()"><i class="bi bi-pencil"></i></button>
</div>
}
</div>
<label for="odometerRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('odometerRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
<div class="input-group-text trip-show d-none">
<button type="button" class="btn btn-sm btn-danger zero-y-padding" onclick="showTripModal()"><i class="bi bi-record-fill"></i></button>
</div>
}
</div>
<label for="odometerRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="odometerRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="odometerRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="odometerRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer d-none trip-modal">
<button type="button" class="btn btn-danger trip-start" onclick="startRecording()" style="margin-right:auto;">@translator.Translate(userLanguage, "Start Recording")</button>
<button type="button" class="btn btn-danger d-none trip-stop" onclick="stopRecording()" style="margin-right:auto;">@translator.Translate(userLanguage, "Stop Recording")</button>
<button type="button" class="btn btn-secondary" onclick="hideTripModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary d-none trip-save" onclick="saveRecordedOdometer()">@translator.Translate(userLanguage, "Save")</button>
</div>
<div class="modal-footer odometer-modal">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteOdometerRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddOdometerRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle()">@translator.Translate(userLanguage,"Add New Odometer Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveOdometerRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Odometer Record")</button>
}
</div>
<script>
//Trip Recording Variables
var tripTimer = undefined; //interval to check GPS Location every 5 seconds.
var tripWakeLock = undefined; //wakelock handler to prevent screen from going to sleep.
var tripLastPosition = undefined; //last coordinates to compare/calculate distance from.
var tripCoordinates = ["Latitude,Longitude"]; //list of coordinates to generate a CSV for.
function getOdometerRecordModelData() {
return { id: @Model.Id}
}
checkTripRecorder();
</script>

View File

@@ -0,0 +1,202 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var userLanguage = userConfig.UserLanguage;
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x=>x.Tab == ImportMode.OdometerRecord);
}
@model List<OdometerRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Odometer Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum-distance">@($"{translator.Translate(userLanguage, "Total Distance")}: {Model.Sum(x => x.DistanceTraveled)}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('odometer-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddOdometerRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Odometer Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('OdometerRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('OdometerRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('odometer-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Date" checked>
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='initialodometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_InitialOdometer" checked>
<label class="form-check-label stretched-link" for="chkCol_InitialOdometer">@translator.Translate(userLanguage, "Initial Odometer")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Odometer" checked>
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='distance' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Distance" checked>
<label class="form-check-label stretched-link" for="chkCol_Distance">@translator.Translate(userLanguage, "Distance")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddOdometerRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Odometer Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@translator.Translate(userLanguage, "Initial Odometer")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" onclick="toggleSort('odometer-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Distance")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (OdometerRecord odometerRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@odometerRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditOdometerRecordModal,@odometerRecord.Id)" data-tags='@string.Join(" ", odometerRecord.Tags)'>
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date">@odometerRecord.Date.ToShortDateString()</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@odometerRecord.InitialMileage</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer" data-record-type="cost">@odometerRecord.Mileage</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" data-record-type="distance">@(odometerRecord.DistanceTraveled == default ? "---" : odometerRecord.DistanceTraveled)</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", odometerRecord.Files)</td>
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = odometerRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="odometerRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="odometerRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleOdometerRecords(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple context-menu-deselect-all dropdown-divider"></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="recalculateDistance()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Recalculate Distance")</span><i class="bi bi-plus-slash-minus"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,49 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model OdometerRecordEditModel
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage,"Edit Multiple Odometer Records")</h5>
<button type="button" class="btn-close" onclick="hideAddOdometerRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<label for="odometerRecordDate">@translator.Translate(userLanguage, "Date")</label>
<div class="input-group">
<input type="text" id="odometerRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="initialOdometerRecordMileage">@translator.Translate(userLanguage, "Initial Odometer")</label>
<input type="number" inputmode="numeric" id="initialOdometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="odometerRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="odometerRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
<select multiple class="form-select" id="odometerRecordTag"></select>
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="odometerRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="odometerRecordNotes" class="form-control" rows="5"></textarea>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideAddOdometerRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="saveMultipleOdometerRecordsToVehicle()">@translator.Translate(userLanguage, "Edit")</button>
</div>
<script>
var recordsToEdit = [];
@foreach(int recordId in Model.RecordIds)
{
@:recordsToEdit.push(@recordId);
}
</script>

View File

@@ -0,0 +1,46 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<SupplyAvailability>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage, "Order Supplies")</h5>
<button type="button" class="btn-close" onclick="hideOrderSupplyModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@if (!Model.Any() || Model.Any(x => x.Missing))
{
<p class="lead">@translator.Translate(userLanguage, "Missing Supplies, Please Delete This Template and Recreate It.")</p>
} else
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-6 text-truncate">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-3 text-truncate">@translator.Translate(userLanguage, "Required")</th>
<th scope="col" class="col-3 text-truncate">@translator.Translate(userLanguage, "In Stock")</th>
</tr>
</thead>
<tbody>
@foreach (SupplyAvailability supplyAvailability in Model)
{
<tr class="d-flex @(supplyAvailability.Insufficient ? "table-danger" : "")">
<td class="col-6 text-truncate">@StaticHelper.TruncateStrings(supplyAvailability.Description)</td>
<td class="col-3 text-truncate">@supplyAvailability.Required.ToString("N2")</td>
<td class="col-3 text-truncate">@supplyAvailability.InStock.ToString("N2")</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideOrderSupplyModal()">@translator.Translate(userLanguage, "Cancel")</button>
</div>

View File

@@ -0,0 +1,55 @@
@model PlanRecord
<div class="taskCard @(Model.Progress == PlanProgress.Done ? "nodrag" : "") text-dark user-select-none mt-2 mb-2" draggable="@(Model.Progress == PlanProgress.Done ? "false" : "true")" ondragstart="dragStart(event, @Model.Id)" onclick="@(Model.Progress == PlanProgress.Done ? $"deletePlanRecord({Model.Id}, true)" : $"showEditPlanRecordModal({Model.Id})")" oncontextmenu="@($"showPlanTableContextMenu(this, {Model.Id}, '{Model.Progress}')")" onmouseup="stopEvent()" ontouchstart="detectPlanItemLongTouch(this, @Model.Id, '@Model.Progress')" ontouchend="detectPlanItemTouchEndPremature(this)">
<div class="card-body">
<div class="row">
<div class="col-12 col-lg-8 text-truncate">
@if (Model.Progress == PlanProgress.Done)
{
<span class="taskCard-title text-truncate"><s>@Model.Description</s></span>
} else
{
<span class="taskCard-title text-truncate">@Model.Description</span>
}
</div>
<div class="col-12 col-lg-4 d-flex align-items-center">
<span class="text-truncate">@Model.Cost.ToString("C2")</span>
</div>
</div>
<div class="row">
@if (Model.ReminderRecordId != default)
{
<div class="col-3 col-md-1">
<span class="badge text-bg-light planner-indicator"><i class="bi bi-bell-fill text-warning"></i></span>
</div>
}
<div class="@(Model.ReminderRecordId != default ? "col-3" : "col-6") col-md-1">
@if (Model.ImportMode == ImportMode.ServiceRecord)
{
<span class="badge text-bg-primary planner-indicator"><i class="bi bi-card-checklist text-white"></i></span>
}
else if (Model.ImportMode == ImportMode.UpgradeRecord)
{
<span class="badge text-bg-success planner-indicator"><i class="bi bi-wrench-adjustable text-white"></i></span>
}
else if (Model.ImportMode == ImportMode.RepairRecord)
{
<span class="badge text-bg-warning planner-indicator"><i class="bi bi-exclamation-octagon text-white"></i></span>
}
</div>
<div class="@(Model.ReminderRecordId != default ? "col-3" : "col-6") col-md-1">
@if (Model.Priority == PlanPriority.Critical)
{
<span class="badge text-bg-danger planner-indicator"><i class="bi bi-fire text-white"></i></span>
}
else if (Model.Priority == PlanPriority.Normal)
{
<span class="badge text-bg-primary planner-indicator"><i class="bi bi-water text-white"></i></span>
}
else if (Model.Priority == PlanPriority.Low)
{
<span class="badge text-bg-info planner-indicator"><i class="bi bi-snow text-white"></i></span>
}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,103 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model PlanRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Plan Record") : translator.Translate(userLanguage, "Edit Plan Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditPlanRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddPlanRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="planRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
@await Html.PartialAsync("Supply/_SupplyStore", new SupplyStore { Tab = "PlanRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="planRecordType">@translator.Translate(userLanguage, "Type")</label>
<select class="form-select" id="planRecordType">
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Service")</!option>
<!option value="RepairRecord" @(Model.ImportMode == ImportMode.RepairRecord ? "selected" : "")>@translator.Translate(userLanguage, "Repair")</!option>
<!option value="UpgradeRecord" @(Model.ImportMode == ImportMode.UpgradeRecord ? "selected" : "")>@translator.Translate(userLanguage, "Upgrade")</!option>
</select>
<label for="planRecordPriority">@translator.Translate(userLanguage, "Priority")</label>
<select class="form-select" id="planRecordPriority">
<!option value="Critical" @(Model.Priority == PlanPriority.Critical ? "selected" : "")>@translator.Translate(userLanguage, "Critical")</!option>
<!option value="Normal" @(Model.Priority == PlanPriority.Normal || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Normal")</!option>
<!option value="Low" @(Model.Priority == PlanPriority.Low ? "selected" : "")>@translator.Translate(userLanguage, "Low")</!option>
</select>
<label for="planRecordProgress">@translator.Translate(userLanguage, "Current Stage")</label>
<select class="form-select" id="planRecordProgress">
<!option value = "Backlog" @(Model.Progress == PlanProgress.Backlog || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Planned")</!option>
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
@if (!isNew)
{
<label>@($"{translator.Translate(userLanguage, "Date Created")}: {Model.DateCreated}")</label>
<label>@($"{translator.Translate(userLanguage, "Last Modified")}: {Model.DateModified}")</label>
}
</div>
<div class="col-md-6 col-12">
<label for="planRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="planRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<button type="button" class="btn btn-danger" onclick="deletePlanRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
@if (isNew)
{
<div class="btn-group">
<button onclick="savePlanRecordToVehicle()" class="btn btn-primary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Add New Plan Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="savePlanRecordTemplate()">@translator.Translate(userLanguage, "Save as Template")</a></li>
</ul>
</div>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="savePlanRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Plan Record")</button>
}
</div>
@await Html.PartialAsync("Supply/_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "planRecordCost" })
<script>
var selectedSupplies = [];
var copySuppliesAttachments = false;
function getPlanRecordModelData() {
return {
id: @Model.Id,
dateCreated: decodeHTMLEntities('@(Model.DateCreated)'),
reminderRecordId: decodeHTMLEntities('@Model.ReminderRecordId'),
createdFromReminder: @Model.CreatedFromReminder.ToString().ToLower(),
isTemplate: false
}
}
</script>

View File

@@ -0,0 +1,90 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model PlanRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage, "Edit Plan Record Template")<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditPlanRecordTemplateModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddPlanRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="planRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
@await Html.PartialAsync("Supply/_SupplyStore", new SupplyStore { Tab = "PlanRecordTemplate", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="planRecordType">@translator.Translate(userLanguage, "Type")</label>
<select class="form-select" id="planRecordType">
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Service")</!option>
<!option value="RepairRecord" @(Model.ImportMode == ImportMode.RepairRecord ? "selected" : "")>@translator.Translate(userLanguage, "Repair")</!option>
<!option value="UpgradeRecord" @(Model.ImportMode == ImportMode.UpgradeRecord ? "selected" : "")>@translator.Translate(userLanguage, "Upgrade")</!option>
</select>
<label for="planRecordPriority">@translator.Translate(userLanguage, "Priority")</label>
<select class="form-select" id="planRecordPriority">
<!option value="Critical" @(Model.Priority == PlanPriority.Critical ? "selected" : "")>@translator.Translate(userLanguage, "Critical")</!option>
<!option value="Normal" @(Model.Priority == PlanPriority.Normal || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Normal")</!option>
<!option value="Low" @(Model.Priority == PlanPriority.Low ? "selected" : "")>@translator.Translate(userLanguage, "Low")</!option>
</select>
<label for="planRecordProgress">@translator.Translate(userLanguage, "Current Stage")</label>
<select class="form-select" id="planRecordProgress">
<!option value = "Backlog" @(Model.Progress == PlanProgress.Backlog || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Planned")</!option>
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="planRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="planRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<button type="button" class="btn btn-danger" onclick="deletePlannerRecordTemplate(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="savePlanRecordTemplate(true)">@translator.Translate(userLanguage, "Edit Plan Record Template")</button>
</div>
@await Html.PartialAsync("Supply/_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "planRecordCost" })
<script>
var selectedSupplies = [];
var copySuppliesAttachments = @Model.CopySuppliesAttachment.ToString().ToLower();
getSelectedSuppliesFromModel();
function getSelectedSuppliesFromModel() {
@foreach(SupplyUsage supplyUsage in Model.Supplies)
{
@:selectedSupplies.push({supplyId: @supplyUsage.SupplyId, quantity: @supplyUsage.Quantity})
}
}
function getPlanRecordModelData() {
return {
id: @Model.Id,
dateCreated: decodeHTMLEntities('@(Model.DateCreated)'),
reminderRecordId: decodeHTMLEntities('@Model.ReminderRecordId'),
createdFromReminder: @Model.CreatedFromReminder.ToString().ToLower(),
isTemplate: true
}
}
</script>

View File

@@ -0,0 +1,91 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<PlanRecordInput>
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage,"Select Template")</h5>
<button type="button" class="btn-close" onclick="hidePlanRecordTemplatesModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@if (Model.Any())
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-8">@translator.Translate(userLanguage,"Description")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Use")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Edit")</th>
</tr>
</thead>
<tbody>
@foreach (PlanRecordInput planRecordTemplate in Model)
{
<tr class="d-flex" id="supplyRows">
<td class="col-8 text-truncate">
@StaticHelper.TruncateStrings(planRecordTemplate.Description)
@if(planRecordTemplate.ReminderRecordId != default)
{
<i class="bi bi-bell ms-2"></i>
}
@if (planRecordTemplate.Files.Any())
{
<i class="bi bi-paperclip ms-2"></i>
}
@if (planRecordTemplate.Supplies.Any())
{
<i class="bi bi-shop ms-2" style="cursor:pointer;"onclick="orderPlanSupplies(@planRecordTemplate.Id)"></i>
}
@if (planRecordTemplate.ImportMode == ImportMode.ServiceRecord)
{
<i class="bi bi-card-checklist ms-2"></i>
}
else if (planRecordTemplate.ImportMode == ImportMode.UpgradeRecord)
{
<i class="bi bi-wrench-adjustable ms-2"></i>
}
else if (planRecordTemplate.ImportMode == ImportMode.RepairRecord)
{
<i class="bi bi-exclamation-octagon ms-2"></i>
}
@if (planRecordTemplate.Priority == PlanPriority.Critical)
{
<i class="bi bi-fire ms-2"></i>
}
else if (planRecordTemplate.Priority == PlanPriority.Normal)
{
<i class="bi bi-water ms-2"></i>
}
else if (planRecordTemplate.Priority == PlanPriority.Low)
{
<i class="bi bi-snow ms-2"></i>
}
</td>
<td class="col-2"><button type="button" class="btn btn-primary" onclick="usePlannerRecordTemplate(@planRecordTemplate.Id)"><i class="bi bi-plus-square"></i></button></td>
<td class="col-2"><button type="button" class="btn btn-warning" onclick="showEditPlanRecordTemplateModal(@planRecordTemplate.Id)"><i class="bi bi-pencil-square"></i></button></td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="row">
<div class="col-12">
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No templates are found.")</h4>
</div>
</div>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hidePlanRecordTemplatesModal()">@translator.Translate(userLanguage, "Cancel")</button>
</div>

View File

@@ -0,0 +1,134 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var userLanguage = userConfig.UserLanguage;
var backLogItems = Model.Where(x => x.Progress == PlanProgress.Backlog).OrderBy(x=>x.Priority);
var inProgressItems = Model.Where(x => x.Progress == PlanProgress.InProgress).OrderBy(x => x.Priority);
var testingItems = Model.Where(x => x.Progress == PlanProgress.Testing).OrderBy(x => x.Priority);
var doneItems = Model.Where(x => x.Progress == PlanProgress.Done).OrderBy(x => x.Priority);
}
@model List<PlanRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage,"# of Plan Records")}: {Model.Count()}")</span>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddPlanRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Plan Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('PlanRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('PlanRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="showPlanRecordTemplatesModal()">@translator.Translate(userLanguage, "View Templates")</a></li>
</ul>
</div>
}
else
{
<button onclick="showAddPlanRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Plan Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer fixed">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<div class="row swimlane">
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Backlog')">
<div class="row sticky-top frosted">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">@translator.Translate(userLanguage,"Planned")</span>
</div>
</div>
@foreach (PlanRecord planRecord in backLogItems)
{
@await Html.PartialAsync("Plan/_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'InProgress')">
<div class="row sticky-top frosted">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">@translator.Translate(userLanguage,"Doing")</span>
</div>
</div>
@foreach (PlanRecord planRecord in inProgressItems)
{
@await Html.PartialAsync("Plan/_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Testing')">
<div class="row sticky-top frosted">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">@translator.Translate(userLanguage,"Testing")</span>
</div>
</div>
@foreach (PlanRecord planRecord in testingItems)
{
@await Html.PartialAsync("Plan/_PlanRecordItem", planRecord)
}
</div>
<div class="col-3 d-flex flex-column swimlane" ondragover="dragOver(event)" ondrop="dropBox(event, 'Done')">
<div class="row sticky-top frosted">
<div class="col-12 d-flex justify-content-center" style="height:5vh;">
<span class="display-7">@translator.Translate(userLanguage,"Done")</span>
</div>
</div>
@foreach (PlanRecord planRecord in doneItems)
{
@await Html.PartialAsync("Plan/_PlanRecordItem", planRecord)
}
</div>
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="planRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="planRecordModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="planRecordTemplateModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="planRecordTemplateModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="planRecordTemplateSupplyOrderModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="planRecordTemplateSupplyOrderModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><h6 class="dropdown-header context-menu-move move-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item context-menu-move move-planned" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Planned")</span><i class="bi bi-list-task"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-doing" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Doing")</span><i class="bi bi-tools"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-testing" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Testing")</span><i class="bi bi-eyeglasses"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-done" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Done")</span><i class="bi bi-check2-all"></i></div></a></li>
<li><hr class="context-menu-move move-header dropdown-divider"></li>
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item context-menu-move move-header context-menu-duplicate-vehicle" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="context-menu-move move-header dropdown-divider"></li>
<li><a class="dropdown-item context-menu-move move-header context-menu-print-tab-sticker" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="context-menu-move move-header dropdown-divider"></li>
<li><a class="dropdown-item context-menu-move move-header text-danger context-menu-delete" href="#"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>

View File

@@ -0,0 +1,143 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model ReminderRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Reminder") : translator.Translate(userLanguage, "Edit Reminder"))</h5>
<button type="button" class="btn-close" onclick="hideAddReminderRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12" id="reminderOptions">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="reminderDescription">@translator.Translate(userLanguage,"Description")</label>
<input type="text" id="reminderDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Reminder Description")" value="@Model.Description">
<label>@translator.Translate(userLanguage,"Remind me on")</label>
<div class="form-check">
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricDate" value="@(ReminderMetric.Date)" checked="@(Model.Metric == ReminderMetric.Date)">
<label class="form-check-label" for="reminderMetricDate">@translator.Translate(userLanguage,"Date")</label>
</div>
<div class="input-group">
<input type="text" id="reminderDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Future Date")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricOdometer" value="@(ReminderMetric.Odometer)" checked="@(Model.Metric == ReminderMetric.Odometer)">
<label class="form-check-label" for="reminderMetricOdometer">@translator.Translate(userLanguage,"Odometer")</label>
</div>
<div class="input-group">
<input type="number" inputmode="numeric" id="reminderMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Future Odometer Reading")" value="@(isNew ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('reminderMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricBoth" value="@(ReminderMetric.Both)" checked="@(Model.Metric == ReminderMetric.Both)">
<label class="form-check-label" for="reminderMetricBoth">@translator.Translate(userLanguage,"Whichever comes first")</label>
</div>
<div class="d-grid"></div>
<label for="reminderRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="reminderRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
</div>
<div class="col-md-6 col-12">
<label for="reminderNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="reminderNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onChange="enableRecurring()" role="switch" id="reminderIsRecurring" checked="@Model.IsRecurring">
<label class="form-check-label" for="reminderIsRecurring">@translator.Translate(userLanguage,"Is Recurring")</label>
</div>
<label for="reminderRecurringMileage">@translator.Translate(userLanguage,"Odometer")</label>
<select class="form-select" onchange="checkCustomMileageInterval()" id="reminderRecurringMileage" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Odometer || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
<!option value="Other" @(Model.ReminderMileageInterval == ReminderMileageInterval.Other ? "selected" : "")>@(Model.ReminderMileageInterval == ReminderMileageInterval.Other && Model.CustomMileageInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMileageInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="FiftyMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyMiles ? "selected" : "")>50 mi. / Km</!option>
<!option value="OneHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredMiles ? "selected" : "")>100 mi. / Km</!option>
<!option value="FiveHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiveHundredMiles ? "selected" : "")>500 mi. / Km</!option>
<!option value="OneThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneThousandMiles ? "selected" : "")>1000 mi. / Km</!option>
<!option value="ThreeThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.ThreeThousandMiles ? "selected" : "")>3000 mi. / Km</!option>
<!option value="FourThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FourThousandMiles ? "selected" : "")>4000 mi. / Km</!option>
<!option value="FiveThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiveThousandMiles || isNew ? "selected" : "")>5000 mi. / Km</!option>
<!option value="SevenThousandFiveHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.SevenThousandFiveHundredMiles ? "selected" : "")>7500 mi. / Km</!option>
<!option value="TenThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.TenThousandMiles ? "selected" : "")>10000 mi. / Km</!option>
<!option value="FifteenThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FifteenThousandMiles ? "selected" : "")>15000 mi. / Km</!option>
<!option value="TwentyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.TwentyThousandMiles ? "selected" : "")>20000 mi. / Km</!option>
<!option value="ThirtyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.ThirtyThousandMiles ? "selected" : "")>30000 mi. / Km</!option>
<!option value="FortyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FortyThousandMiles ? "selected" : "")>40000 mi. / Km</!option>
<!option value="FiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyThousandMiles ? "selected" : "")>50000 mi. / Km</!option>
<!option value="SixtyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.SixtyThousandMiles ? "selected" : "")>60000 mi. / Km</!option>
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
</select>
<label for="reminderRecurringMonth">@translator.Translate(userLanguage, "Time")</label>
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Date || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
<!option value="Other" @(Model.ReminderMonthInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.ReminderMonthInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval} {Model.CustomMonthIntervalUnit}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="OneMonth" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage, "1 Month")</!option>
<!option value="ThreeMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage,"3 Months")</!option>
<!option value="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage,"6 Months")</!option>
<!option value="OneYear" @(Model.ReminderMonthInterval == ReminderMonthInterval.OneYear ? "selected" : "")>@translator.Translate(userLanguage, "1 Year")</!option>
<!option value="TwoYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.TwoYears ? "selected" : "")>@translator.Translate(userLanguage, "2 Years")</!option>
<!option value="ThreeYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.ThreeYears ? "selected" : "")>@translator.Translate(userLanguage, "3 Years")</!option>
<!option value="FiveYears" @(Model.ReminderMonthInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>@translator.Translate(userLanguage, "5 Years")</!option>
</select>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" onchange="toggleCustomThresholds()" id="reminderUseCustomThresholds" checked="@Model.UseCustomThresholds">
<label class="form-check-label" for="reminderUseCustomThresholds">@translator.Translate(userLanguage, "Use Custom Thresholds")</label>
</div>
<div class="collapse @(Model.UseCustomThresholds ? "show" : "")" id="reminderCustomThresholds">
<div>
<label for="reminderUrgentDays">@translator.Translate(userLanguage, "Urgent(Days)")</label>
<input type="text" id="reminderUrgentDays" class="form-control" placeholder="@translator.Translate(userLanguage, "Urgent")" value="@Model.CustomThresholds.UrgentDays">
<label for="reminderVeryUrgentDays">@translator.Translate(userLanguage, "Very Urgent(Days)")</label>
<input type="text" id="reminderVeryUrgentDays" class="form-control" placeholder="@translator.Translate(userLanguage, "Very Urgent")" value="@Model.CustomThresholds.VeryUrgentDays">
<label for="reminderUrgentDistance">@translator.Translate(userLanguage, "Urgent(Distance)")</label>
<input type="text" id="reminderUrgentDistance" class="form-control" placeholder="@translator.Translate(userLanguage, "Urgent")" value="@Model.CustomThresholds.UrgentDistance">
<label for="reminderVeryUrgentDistance">@translator.Translate(userLanguage, "Very Urgent(Distance)")</label>
<input type="text" id="reminderVeryUrgentDistance" class="form-control" placeholder="@translator.Translate(userLanguage, "Very Urgent")" value="@Model.CustomThresholds.VeryUrgentDistance">
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.IsRecurring)
{
<button type="button" class="btn btn-warning" onclick="createPlanRecordFromReminder(@Model.Id)">@translator.Translate(userLanguage, "Plan")</button>
}
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddReminderRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveReminderRecordToVehicle()">@translator.Translate(userLanguage, "Add New Reminder")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveReminderRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Reminder")</button>
}
</div>
<script>
var customMileageInterval = @Model.CustomMileageInterval;
var customMonthInterval = @Model.CustomMonthInterval;
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
function getReminderRecordModelData() {
return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')}
}
</script>

View File

@@ -0,0 +1,246 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<ReminderRecordViewModel>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var hasRefresh = Model.Where(x => (x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue) && x.IsRecurring).Any();
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var enableCsvImports = userConfig.EnableCsvImports;
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.ReminderRecord);
}
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Reminders")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-secondary" data-aggregate-type="pastdue-count">@($"{translator.Translate(userLanguage, "Past Due")}: {Model.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()}")</span>
<span class="ms-2 badge bg-danger" data-aggregate-type="veryurgent-count">@($"{translator.Translate(userLanguage, "Very Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count()}")</span>
<span class="ms-2 badge text-bg-warning" data-aggregate-type="urgent-count">@($"{translator.Translate(userLanguage, "Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.Urgent).Count()}")</span>
<span class="ms-2 badge bg-success" data-aggregate-type="noturgent-count">@($"{translator.Translate(userLanguage, "Not Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count()}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterReminderTable(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Reminder")</button>
<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="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('reminder-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='urgency' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Urgency" checked>
<label class="form-check-label stretched-link" for="chkCol_Urgency">@translator.Translate(userLanguage, "Urgency")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='metric' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Metric" checked>
<label class="form-check-label stretched-link" for="chkCol_Metric">@translator.Translate(userLanguage, "Metric")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Date">
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Odometer">
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='duedays' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_DueDays">
<label class="form-check-label stretched-link" for="chkCol_DueDays">@translator.Translate(userLanguage, "Due Days")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='duedistance' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_DueDistance">
<label class="form-check-label stretched-link" for="chkCol_DueDistance">@translator.Translate(userLanguage, "Due Distance")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@if(hasRefresh){
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='done' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Done" checked>
<label class="form-check-label stretched-link" for="chkCol_Done">@translator.Translate(userLanguage, "Done")</label>
</div>
</li>
}
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='delete' onChange="showTableColumns(this, 'ReminderRecord')" type="checkbox" id="chkCol_Delete" checked>
<label class="form-check-label stretched-link" for="chkCol_Delete">@translator.Translate(userLanguage, "Delete")</label>
</div>
</li>
</ul>
</div>
}
else
{
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Reminder")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" data-column="urgency" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Urgency")</th>
<th scope="col" data-column="metric" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Metric")</th>
<th scope="col" data-column="date" style="display:none;" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" data-column="odometer" style="display:none;" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" data-column="duedays" style="display:none;" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Due Days")</th>
<th scope="col" data-column="duedistance" style="display:none;" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@translator.Translate(userLanguage, "Due Distance")</th>
<th scope="col" data-column="description" class="flex-grow-1 flex-shrink-1 text-truncate @(hasRefresh ? "col-3" : "col-4")">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@translator.Translate(userLanguage, "Notes")</th>
@if (hasRefresh)
{
<th scope="col" data-column="done" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Done")</th>
}
<th scope="col" data-column="delete" class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody>
@foreach (ReminderRecordViewModel reminderRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@reminderRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditReminderRecordModal,@reminderRecord.Id)" data-tags='@string.Join(" ", reminderRecord.Tags)'>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="urgency">
@if (reminderRecord.Urgency == ReminderUrgency.VeryUrgent)
{
<span class="badge text-bg-danger">@translator.Translate(userLanguage, "Very Urgent")</span>
}
else if (reminderRecord.Urgency == ReminderUrgency.Urgent)
{
<span class="badge text-bg-warning">@translator.Translate(userLanguage, "Urgent")</span>
}
else if (reminderRecord.Urgency == ReminderUrgency.PastDue)
{
<span class="badge text-bg-secondary">@translator.Translate(userLanguage, "Past Due")</span>
}
else
{
<span class="badge text-bg-success">@translator.Translate(userLanguage, "Not Urgent")</span>
}
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" data-column="metric" data-record-type="cost">
@if (reminderRecord.Metric == ReminderMetric.Date)
{
@reminderRecord.Date.ToShortDateString()
}
else if (reminderRecord.Metric == ReminderMetric.Odometer)
{
@reminderRecord.Mileage
}
else
{
@reminderRecord.Metric
}
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" style="display:none;" data-column="date">
@(reminderRecord.UserMetric == ReminderMetric.Both || reminderRecord.UserMetric == ReminderMetric.Date ? reminderRecord.Date.ToShortDateString() : "---")
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" style="display:none;" data-column="odometer">
@(reminderRecord.UserMetric == ReminderMetric.Both || reminderRecord.UserMetric == ReminderMetric.Odometer ? reminderRecord.Mileage : "---")
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" style="display:none;" data-column="duedays">
@(reminderRecord.UserMetric == ReminderMetric.Both || reminderRecord.UserMetric == ReminderMetric.Date ? reminderRecord.DueDays : "---")
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" style="display:none;" data-column="duedistance">
@(reminderRecord.UserMetric == ReminderMetric.Both || reminderRecord.UserMetric == ReminderMetric.Odometer ? reminderRecord.DueMileage : "---")
</td>
<td data-column="description" class="flex-grow-1 flex-shrink-1 text-truncate @(hasRefresh ? "col-3" : "col-4")">@reminderRecord.Description</td>
<td data-column="notes" class="flex-grow-1 flex-shrink-1 col-2 text-truncate">@StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
@if (hasRefresh)
{
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint" data-column="done">
@if((reminderRecord.Urgency == ReminderUrgency.VeryUrgent || reminderRecord.Urgency == ReminderUrgency.PastDue) && reminderRecord.IsRecurring)
{
<button type="button" class="btn btn-secondary" onclick="markDoneReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-check-lg"></i></button>
}
</td>
}
<td class="flex-grow-1 flex-shrink-1 col-2 text-truncate hideOnPrint" data-column="delete">
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
</td>
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="planRecordModal" tabindex="-1" role="dialog" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="planRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'ReminderRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,116 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model ServiceRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Service Record") : translator.Translate(userLanguage, "Edit Service Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditServiceRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddServiceRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="serviceRecordDate">@translator.Translate(userLanguage,"Date")</label>
<div class="input-group">
<input type="text" id="serviceRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date service was performed")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="serviceRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="serviceRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when serviced")" value="@(isNew || Model.Mileage == default ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('serviceRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="serviceRecordDescription">@translator.Translate(userLanguage,"Description")</label>
<input type="text" id="serviceRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) serviced(i.e. Oil Change)")" value="@Model.Description">
@if (isNew)
{
<div class="row">
<div class="col-12">
<a onclick="showRecurringReminderSelector('serviceRecordDescription', 'serviceRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
</div>
</div>
}
<label for="serviceRecordCost">@translator.Translate(userLanguage,"Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="serviceRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the service")" value="@(isNew ? "" : Model.Cost)">
@await Html.PartialAsync("Supply/_SupplyStore", new SupplyStore { Tab = "ServiceRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="serviceRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="serviceRecordTag">
@foreach(string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="serviceRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="serviceRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (isNew)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
<label class="form-check-label" for="addReminderCheck">
@translator.Translate(userLanguage, "Add Reminder")
</label>
</div>
}
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteServiceRecord(@Model.Id)">@translator.Translate(userLanguage,"Delete")</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">@translator.Translate(userLanguage,"Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'ServiceRecord', 'RepairRecord')">@translator.Translate(userLanguage,"Repairs")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'ServiceRecord', 'UpgradeRecord')">@translator.Translate(userLanguage,"Upgrades")</a></li>
</ul>
</div>
}
<button type="button" class="btn btn-secondary" onclick="hideAddServiceRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveServiceRecordToVehicle()">@translator.Translate(userLanguage,"Add New Service Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveServiceRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Service Record")</button>
}
</div>
@await Html.PartialAsync("Supply/_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "serviceRecordCost" })
<script>
var selectedSupplies = [];
var recurringReminderRecordId = [];
var copySuppliesAttachments = false;
function getServiceRecordModelData(){
return {id: @Model.Id}
}
</script>

View File

@@ -0,0 +1,205 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var userLanguage = userConfig.UserLanguage;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.ServiceRecord);
}
@model List<ServiceRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Service Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('servicerecord-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Service Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('ServiceRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('ServiceRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('servicerecord-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Date" checked>
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Odometer" checked>
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Cost" checked>
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'ServiceRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Service Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (ServiceRecord serviceRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@serviceRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditServiceRecordModal,@serviceRecord.Id)" data-tags='@string.Join(" ", serviceRecord.Tags)'>
<td class="col-2 col-xl-1 flex-grow-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(serviceRecord.Date)">@serviceRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(serviceRecord.Mileage == default ? "---" : serviceRecord.Mileage.ToString())</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@serviceRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(serviceRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", serviceRecord.Files)</td>
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1" data-column="notes">@StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = serviceRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute)){
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
} else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="serviceRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="serviceRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'ServiceRecord', 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Repairs")</span><i class="bi bi-exclamation-octagon"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'ServiceRecord', 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Upgrades")</span><i class="bi bi-wrench-adjustable"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,92 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model SupplyRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Supply Record") : translator.Translate(userLanguage, "Edit Supply Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditSupplyRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddSupplyRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="supplyRecordDate">@translator.Translate(userLanguage,"Date")</label>
<div class="input-group">
<input type="text" id="supplyRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date purchased")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="supplyRecordPartNumber">@translator.Translate(userLanguage,"Part Number")</label>
<input type="text" id="supplyRecordPartNumber" class="form-control" placeholder="@translator.Translate(userLanguage,"Part #/Model #/SKU #")" value="@(isNew ? "" : Model.PartNumber)">
<label for="supplyRecordDescription">@translator.Translate(userLanguage,"Description")</label>
<input type="text" id="supplyRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of the Part/Supplies")" value="@Model.Description">
<label for="supplyRecordSupplier">@translator.Translate(userLanguage,"Supplier/Vendor")</label>
<input type="text" id="supplyRecordSupplier" class="form-control" placeholder="@translator.Translate(userLanguage,"Part Supplier")" value="@Model.PartSupplier">
<div class="row">
<div class="col-md-6 col-12">
<label for="supplyRecordQuantity">@translator.Translate(userLanguage,"Quantity")</label>
<div class="input-group">
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"Quantity")" value="@(isNew ? "" : Model.Quantity.ToString("N2"))">
<div class="input-group-text">
<button type="button" class="btn btn-sm zero-y-padding btn-primary" onclick="replenishSupplies()"><i class="bi bi-plus"></i></button>
</div>
</div>
</div>
<div class="col-md-6 col-12">
<label for="supplyRecordCost">@translator.Translate(userLanguage,"Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="supplyRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost")" value="@(isNew ? "" : Model.Cost)">
</div>
</div>
<label for="supplyRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="supplyRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="supplyRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="supplyRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-clock-history"></i></button>
}
<button type="button" class="btn btn-danger" onclick="deleteSupplyRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddSupplyRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle()">@translator.Translate(userLanguage,"Add New Supply Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Supply Record")</button>
}
</div>
@await Html.PartialAsync("Supply/_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "" })
<script>
function getSupplyRecordModelData() {
return { id: @Model.Id}
}
</script>

View File

@@ -0,0 +1,213 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.SupplyRecord);
}
@model List<SupplyRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Supply Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage, "Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('supply-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Supply Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('SupplyRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('SupplyRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage,"Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('supply-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Date" checked>
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='partnumber' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_PartNumber" checked>
<label class="form-check-label stretched-link" for="chkCol_PartNumber">@translator.Translate(userLanguage, "Part Number")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='supplier' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Supplier" checked>
<label class="form-check-label stretched-link" for="chkCol_Supplier">@translator.Translate(userLanguage, "Supplier")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='quantity' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Quantity" checked>
<label class="form-check-label stretched-link" for="chkCol_Quantity">@translator.Translate(userLanguage, "Quantity")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Cost" checked>
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'SupplyRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage,"Add Supply Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 flex-grow-1 col-xl-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="partnumber">@translator.Translate(userLanguage, "Part #")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="supplier">@translator.Translate(userLanguage, "Supplier")</th>
<th scope="col" class="col-2 flex-grow-1 col-xl-3 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="quantity" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Quantity")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (SupplyRecord supplyRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@supplyRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditSupplyRecordModal,@supplyRecord.Id)" data-tags='@string.Join(" ", supplyRecord.Tags)'>
<td class="col-2 flex-grow-1 col-xl-1 text-truncate" data-column="date">@supplyRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="partnumber">@supplyRecord.PartNumber</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="supplier">@supplyRecord.PartSupplier</td>
<td class="col-2 flex-grow-1 col-xl-3 text-truncate" data-column="description">@supplyRecord.Description</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="quantity">@supplyRecord.Quantity</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(supplyRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", supplyRecord.Files)</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = supplyRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="supplyRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="supplyRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'SupplyRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,106 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model SupplyRequisitionHistory
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var showDelete = Model.RequisitionHistory.All(x => x.Id != default);
var showPartNumber = Model.RequisitionHistory.Any(x => !string.IsNullOrWhiteSpace(x.PartNumber));
}
<script>
var supplyUsageHistory = [];
var deletedSupplyUsageHistory = [];
function subtractFromCostInput(costToSubtract){
let costInputId = '@Model.CostInputId';
let costInput = $(`#${costInputId}`);
let newCostAmount = globalParseFloat(costInput.val()) - globalParseFloat(costToSubtract);
if (newCostAmount < 0){
newCostAmount = 0;
}
costInput.val(globalFloatToString(newCostAmount.toFixed(2)));
}
function deleteSupplyUsageHistory(supplyId, quantity, cost, supplyRow){
deletedSupplyUsageHistory.push({
id: decodeHTMLEntities(supplyId),
quantity: decodeHTMLEntities(quantity),
cost: decodeHTMLEntities(cost)
});
let supplyIndexToRemove = supplyUsageHistory.findIndex(x=>x.id == decodeHTMLEntities(supplyId) && x.quantity == decodeHTMLEntities(quantity) && x.cost == decodeHTMLEntities(cost));
if (supplyIndexToRemove > -1){
supplyUsageHistory.splice(supplyIndexToRemove, 1);
}
$(supplyRow).parents(".supply-row").remove();
//update cost input value
subtractFromCostInput(decodeHTMLEntities(cost));
}
</script>
<div id="supplyUsageHistoryModalContainer" class="d-none">
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage, "Supply Requisition History")</h5>
</div>
<div class="modal-body">
@if (Model.RequisitionHistory.Any())
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Date")</th>
@if(showPartNumber){
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
<th scope="col" class="@(showDelete ? "col-2" : "col-4")">@translator.Translate(userLanguage, "Description")</th>
} else
{
<th scope="col" class="@(showDelete ? "col-4" : "col-6")">@translator.Translate(userLanguage, "Description")</th>
}
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Quantity")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Cost")</th>
@if (showDelete){
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
}
</tr>
</thead>
<tbody>
@foreach (SupplyUsageHistory usageHistory in Model.RequisitionHistory)
{
<script>
supplyUsageHistory.push({ id: decodeHTMLEntities("@usageHistory.Id"), date: decodeHTMLEntities("@usageHistory.Date.ToShortDateString()"), partNumber: decodeHTMLEntities('@usageHistory.PartNumber'), description: decodeHTMLEntities("@usageHistory.Description"), quantity: decodeHTMLEntities("@usageHistory.Quantity.ToString("F")"), cost: decodeHTMLEntities("@usageHistory.Cost.ToString("F")") })
</script>
<tr class="d-flex supply-row">
<td class="col-2">@StaticHelper.TruncateStrings(usageHistory.Date.ToShortDateString())</td>
@if (showPartNumber)
{
<td class="col-2 text-truncate">@StaticHelper.TruncateStrings(usageHistory.PartNumber)</td>
<td class="@(showDelete ? "col-2" : "col-4") text-truncate">@StaticHelper.TruncateStrings(usageHistory.Description)</td>
} else
{
<td class="@(showDelete ? "col-4" : "col-6") text-truncate">@StaticHelper.TruncateStrings(usageHistory.Description, 50)</td>
}
<td class="col-2">@usageHistory.Quantity.ToString("F")</td>
<td class="col-2">@usageHistory.Cost.ToString("C2")</td>
@if (showDelete){
<td class="col-2">
<button type="button" class="btn btn-danger" onclick="deleteSupplyUsageHistory('@usageHistory.Id.ToString()', '@usageHistory.Quantity.ToString("F")', '@usageHistory.Cost.ToString("F")', this)"><i class="bi bi-trash"></i></button>
</td>
}
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="row">
<div class="col-12">
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No supply requisitions in history")</h4>
</div>
</div>
</div>
}
</div>
</div>

View File

@@ -0,0 +1,133 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model SupplyStore
<div class="row">
<div class="col-12">
<a onclick="showSuppliesModal()" class="btn btn-link">@translator.Translate(userLanguage,Model.AdditionalSupplies ? "Choose Additional Supplies" : "Choose Supplies")</a>
</div>
</div>
<script>
resetSuppliesModal();
function GetCaller() {
return {
tab: '@Model.Tab',
addToSum: @Model.AdditionalSupplies.ToString().ToLower()
};
}
function resetSuppliesModal() {
$("#inputSuppliesModalContent").html("");
}
function setCostInputWithSupplySum(selectedSum, input){
var addToSum = GetCaller().addToSum;
if (addToSum){
//sum of all requisitioned supplies
var currentSum = supplyUsageHistory.length > 0 ? supplyUsageHistory.map(x => globalParseFloat(x.cost)).reduce((a,b) => a + b) : 0;
selectedSum = globalParseFloat(selectedSum);
var newSum = currentSum + selectedSum;
input.val(globalFloatToString(newSum.toFixed(2)));
} else {
input.val(selectedSum);
}
}
function selectSupplies() {
var selectedSupplyResult = getSuppliesAndQuantity();
var caller = GetCaller().tab;
switch (caller) {
case "ServiceRecord":
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#serviceRecordCost'))
break;
case "RepairRecord":
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#collisionRecordCost'))
break;
case "UpgradeRecord":
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#upgradeRecordCost'))
break;
case "PlanRecord":
case "PlanRecordTemplate":
setCostInputWithSupplySum(selectedSupplyResult.totalSum, $('#planRecordCost'))
break;
}
selectedSupplies = getSuppliesAndQuantity().selectedSupplies;
copySuppliesAttachments = $("#inputCopySuppliesAttachments").is(':checked');
hideSuppliesModal();
}
function hideParentModal(){
var caller = GetCaller().tab;
switch (caller) {
case "ServiceRecord":
$('#serviceRecordModal').modal('hide');
break;
case "RepairRecord":
$('#collisionRecordModal').modal('hide');
break;
case "UpgradeRecord":
$('#upgradeRecordModal').modal('hide');
break;
case "PlanRecord":
case "PlanRecordTemplate":
$('#planRecordModal').modal('hide');
break;
}
}
function showParentModal() {
var caller = GetCaller().tab;
switch (caller) {
case "ServiceRecord":
$('#serviceRecordModal').modal('show');
break;
case "RepairRecord":
$('#collisionRecordModal').modal('show');
break;
case "UpgradeRecord":
$('#upgradeRecordModal').modal('show');
break;
case "PlanRecord":
case "PlanRecordTemplate":
$('#planRecordModal').modal('show');
break;
}
}
function showSuppliesModal() {
if ($("#inputSuppliesModalContent").html() == "") {
getSupplies();
} else {
hideParentModal();
$('#inputSuppliesModal').modal('show');
}
}
function getSupplies() {
var caller = GetCaller().tab;
if (caller == 'PlanRecordTemplate') {
var planRecordTemplateId = getPlanRecordModelData().id;
$.get(`/Vehicle/GetSupplyRecordsForPlanRecordTemplate?planRecordTemplateId=${planRecordTemplateId}`, function (data) {
if (data) {
hideParentModal();
$("#inputSuppliesModalContent").html(data);
$('#inputSuppliesModal').modal('show');
recalculateTotal();
if (copySuppliesAttachments) {
$('#inputCopySuppliesAttachments').attr('checked', true);
}
}
});
} else {
var vehicleId = GetVehicleId().vehicleId;
$.get(`/Vehicle/GetSupplyRecordsForRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {
hideParentModal();
$("#inputSuppliesModalContent").html(data);
$('#inputSuppliesModal').modal('show');
}
});
}
}
function hideSuppliesModal() {
$('#inputSuppliesModal').modal('hide');
showParentModal();
}
</script>

View File

@@ -0,0 +1,147 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var recordTags = Model.Supplies.SelectMany(x => x.Tags).Distinct();
}
@model SupplyUsageViewModel
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage,"Select Supplies")</h5>
<button type="button" class="btn-close" onclick="hideSuppliesModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@if (Model.Supplies.Any())
{
<div class="row">
<div class="col-12" style="max-height:50vh; overflow-y:auto;" id="supplies-table">
<div class="alert alert-warning" role="alert">
@translator.Translate(userLanguage,"Supplies are requisitioned immediately after the record is saved.")
</div>
<div class="d-flex align-items-center flex-wrap">
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('supplies-table', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-1"></th>
<th scope="col" class="col-3 col-sm-2 text-truncate flex-grow">@translator.Translate(userLanguage,"Quantity")</th>
<th scope="col" class="col-1 col-sm-2 text-truncate flex-shrink">@translator.Translate(userLanguage, "In Stock")</th>
<th scope="col" class="col-2 text-truncate flex-shrink">@translator.Translate(userLanguage, "Part Number")</th>
<th scope="col" class="col-3 text-truncate flex-shrink">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 text-truncate flex-shrink">@translator.Translate(userLanguage, "Unit Cost")</th>
</tr>
</thead>
<tbody>
@foreach (SupplyRecord supplyRecord in Model.Supplies)
{
var supplyUsage = Model.Usage.Where(x => x.SupplyId == supplyRecord.Id).SingleOrDefault();
<tr class="d-flex" id="supplyRows" data-tags='@string.Join(" ", supplyRecord.Tags)'>
<td class="col-1"><input class="form-check-input" type="checkbox" onchange="toggleQuantityFieldDisabled(this)" value="@supplyRecord.Id" @(supplyUsage == default ? "" : "checked")></td>
<td class="col-3 col-sm-2 flex-grow text-truncate supplyquantityinput"><input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" @(supplyUsage == default ? "disabled" : "") value="@(supplyUsage == default ? "" : supplyUsage.Quantity)" onchange="recalculateTotal()" class="form-control"></td>
<td class="col-1 col-sm-2 flex-shrink text-truncate supplyquantity">@supplyRecord.Quantity</td>
<td class="col-2 flex-shrink text-truncate">@StaticHelper.TruncateStrings(supplyRecord.PartNumber)</td>
<td class="col-3 flex-shrink text-truncate">@StaticHelper.TruncateStrings(supplyRecord.Description)</td>
<td class="col-2 flex-shrink text-truncate supplyprice">@((supplyRecord.Quantity > 0 ? supplyRecord.Cost / supplyRecord.Quantity : 0).ToString("F"))</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="row">
<div class="col-12">
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No supplies with quantities greater than 0 is found.")</h4>
</div>
</div>
</div>
}
</div>
<div class="modal-footer">
<span id="supplySumLabel" style="margin-right:auto;">Total: 0.00</span>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputCopySuppliesAttachments">
<label class="form-check-label" for="inputCopySuppliesAttachments">@translator.Translate(userLanguage, "Copy Attachments")</label>
</div>
<button type="button" class="btn btn-secondary" onclick="hideSuppliesModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" disabled id="selectSuppliesButton" onclick="selectSupplies()">@translator.Translate(userLanguage, "Select")</button>
</div>
<script>
function recalculateTotal() {
setDebounce(getSuppliesAndQuantity);
}
function toggleQuantityFieldDisabled(e) {
var textField = getTextFieldFromCheckBox(e);
var isChecked = $(e).is(":checked");
textField.attr('disabled', !isChecked);
if (!isChecked) {
textField.removeClass("is-invalid");
}
recalculateTotal();
}
function getTextFieldFromCheckBox(elem) {
var textField = $(elem.parentElement.parentElement).find('.supplyquantityinput input[type=text]')[0];
return $(textField);
}
function getInStockFieldFromCheckBox(elem) {
var textField = $(elem.parentElement.parentElement).find('.supplyquantity')[0];
return $(textField);
}
function getPriceFieldFromCheckBox(elem) {
var textField = $(elem.parentElement.parentElement).find('.supplyprice')[0];
return $(textField);
}
function getSuppliesAndQuantity() {
var totalSum = 0;
var hasError = false;
var selectedSupplies = $("#supplyRows :checked").map(function () {
var textField = getTextFieldFromCheckBox(this);
var inStock = getInStockFieldFromCheckBox(this);
var priceField = getPriceFieldFromCheckBox(this);
var requestedQuantity = globalParseFloat(textField.val());
var inStockQuantity = globalParseFloat(inStock.text());
var unitPrice = globalParseFloat(priceField.text());
//validation
if (isNaN(requestedQuantity) || requestedQuantity > inStockQuantity || requestedQuantity <= 0) {
textField.addClass("is-invalid");
hasError = true;
} else {
textField.removeClass("is-invalid");
}
//calculate sum.
var sum = requestedQuantity * unitPrice;
totalSum += sum;
return {
supplyId: this.value,
quantity: textField.val()
};
});
if (isNaN(totalSum) || hasError) {
$("#supplySumLabel").text(`Total: 0.00`);
} else {
totalSum = totalSum.toFixed(2);
var parsedFloat = globalFloatToString(totalSum);
$("#supplySumLabel").text(`Total: ${parsedFloat}`);
}
$("#selectSuppliesButton").attr('disabled', (hasError || selectedSupplies.toArray().length == 0));
if (!hasError) {
return {
totalSum: globalFloatToString(totalSum),
selectedSupplies: selectedSupplies.toArray()
};
} else {
return {
totalSum: 0,
selectedSupplies: []
}
}
}
</script>

View File

@@ -0,0 +1,105 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model TaxRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Tax Record") : translator.Translate(userLanguage, "Edit Tax Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditTaxRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddTaxRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="taxRecordDate">@translator.Translate(userLanguage,"Date")</label>
<div class="input-group">
<input type="text" id="taxRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date tax was paid")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="taxRecordDescription">@translator.Translate(userLanguage,"Description")</label>
<input type="text" id="taxRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of tax paid(i.e. Registration)")" value="@Model.Description">
@if (isNew)
{
<div class="row">
<div class="col-12">
<a onclick="showRecurringReminderSelector('taxRecordDescription', 'taxRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
</div>
</div>
}
<label for="taxRecordCost">@translator.Translate(userLanguage,"Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="taxRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of tax paid")" value="@(isNew? "" : Model.Cost)">
<label for="taxRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="taxRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="taxRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="taxRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onChange="enableTaxRecurring()" role="switch" id="taxIsRecurring" checked="@Model.IsRecurring">
<label class="form-check-label" for="taxIsRecurring">@translator.Translate(userLanguage,"Is Recurring")</label>
</div>
<label for="taxRecurringMonth">@translator.Translate(userLanguage,"Time")</label>
<select class="form-select" onchange="checkCustomMonthIntervalForTax()" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
<!option value="Other" @(Model.RecurringInterval == ReminderMonthInterval.Other ? "selected" : "")>@(Model.RecurringInterval == ReminderMonthInterval.Other && Model.CustomMonthInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMonthInterval} {Model.CustomMonthIntervalUnit}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="OneMonth" @(Model.RecurringInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>@translator.Translate(userLanguage,"1 Month")</!option>
<!option value="ThreeMonths" @(Model.RecurringInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>@translator.Translate(userLanguage, "3 Months")</!option>
<!option value="SixMonths" @(Model.RecurringInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage, "6 Months")</!option>
<!option value="OneYear" @(Model.RecurringInterval == ReminderMonthInterval.OneYear ? "selected" : "")>@translator.Translate(userLanguage, "1 Year")</!option>
<!option value="TwoYears" @(Model.RecurringInterval == ReminderMonthInterval.TwoYears ? "selected" : "")>@translator.Translate(userLanguage, "2 Years")</!option>
<!option value="ThreeYears" @(Model.RecurringInterval == ReminderMonthInterval.ThreeYears ? "selected" : "")>@translator.Translate(userLanguage, "3 Years")</!option>
<!option value="FiveYears" @(Model.RecurringInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>@translator.Translate(userLanguage, "5 Years")</!option>
</select>
@if (isNew)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
<label class="form-check-label" for="addReminderCheck">
@translator.Translate(userLanguage, "Add Reminder")
</label>
</div>
}
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteTaxRecord(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage,"Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddTaxRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveTaxRecordToVehicle()">@translator.Translate(userLanguage,"Add New Tax Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveTaxRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Tax Record")</button>
}
</div>
<script>
var customMonthInterval = @Model.CustomMonthInterval;
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
var recurringReminderRecordId = [];
function getTaxRecordModelData() {
return { id: @Model.Id, monthInterval: decodeHTMLEntities('@Model.RecurringInterval.ToString()') }
}
</script>

View File

@@ -0,0 +1,189 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.TaxRecord);
}
@model List<TaxRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Tax Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('tax-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddTaxRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Tax Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('TaxRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('TaxRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('tax-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Date" checked>
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Cost" checked>
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'TaxRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddTaxRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Tax Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-3 flex-grow-1 col-xl-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-4 flex-grow-1 col-xl-6 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('tax-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (TaxRecord taxRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@taxRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditTaxRecordModal,@taxRecord.Id)" data-tags='@string.Join(" ", taxRecord.Tags)'>
<td class="col-3 flex-grow-1 col-xl-1 text-truncate" data-column="date">@taxRecord.Date.ToShortDateString()</td>
<td class="col-4 flex-grow-1 col-xl-6 text-truncate" data-column="description">@taxRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(taxRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", taxRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(taxRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = taxRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="taxRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="taxRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'TaxRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,116 @@
@inject IConfigHelper config
@inject ITranslationHelper translator
@using MotoVaultPro.Helper
@model UpgradeRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Upgrade Record") : translator.Translate(userLanguage, "Edit Upgrade Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditUpgradeRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddUpgradeRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="upgradeRecordDate">@translator.Translate(userLanguage, "Date")</label>
<div class="input-group">
<input type="text" id="upgradeRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date upgrade/mods was installed")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="upgradeRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="upgradeRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when upgraded/modded")" value="@(isNew || Model.Mileage == default ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('upgradeRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="upgradeRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="upgradeRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) upgraded/modded")" value="@Model.Description">
@if (isNew)
{
<div class="row">
<div class="col-12">
<a onclick="showRecurringReminderSelector('upgradeRecordDescription', 'upgradeRecordNotes')" class="btn btn-link">@translator.Translate(userLanguage, "Select Reminder")</a>
</div>
</div>
}
<label for="upgradeRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="upgradeRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the upgrade/mods")" value="@(isNew ? "" : Model.Cost)">
@await Html.PartialAsync("Supply/_SupplyStore", new SupplyStore { Tab = "UpgradeRecord", AdditionalSupplies = Model.RequisitionHistory.Any() })
<label for="upgradeRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="upgradeRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="upgradeRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="upgradeRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (isNew)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
<label class="form-check-label" for="addReminderCheck">
@translator.Translate(userLanguage, "Add Reminder")
</label>
</div>
}
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
@await Html.PartialAsync("_FileUploader", Model.Files.Any())
</div>
<div id="filesPendingUpload"></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteUpgradeRecord(@Model.Id)">@translator.Translate(userLanguage, "Delete")</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'UpgradeRecord', 'ServiceRecord')">@translator.Translate(userLanguage, "Service Records")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'UpgradeRecord', 'RepairRecord')">@translator.Translate(userLanguage, "Repairs")</a></li>
</ul>
</div>
}
<button type="button" class="btn btn-secondary" onclick="hideAddUpgradeRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveUpgradeRecordToVehicle()">@translator.Translate(userLanguage, "Add New Upgrade Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveUpgradeRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Upgrade Record")</button>
}
</div>
@await Html.PartialAsync("Supply/_SupplyRequisitionHistory", new SupplyRequisitionHistory { RequisitionHistory = Model.RequisitionHistory, CostInputId = "upgradeRecordCost" })
<script>
var selectedSupplies = [];
var recurringReminderRecordId = [];
var copySuppliesAttachments = false;
function getUpgradeRecordModelData() {
return { id: @Model.Id}
}
</script>

View File

@@ -0,0 +1,206 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var enableCsvImports = userConfig.EnableCsvImports;
var hideZero = userConfig.HideZero;
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
var extraFields = new List<string>();
if (userConfig.EnableExtraFieldColumns)
{
extraFields = Model.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct().ToList();
}
var userColumnPreferences = userConfig.UserColumnPreferences.Where(x => x.Tab == ImportMode.UpgradeRecord);
}
@model List<UpgradeRecord>
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage,"# of Upgrade Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum">@($"{translator.Translate(userLanguage,"Total")}: {Model.Sum(x => x.Cost).ToString("C")}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('upgrade-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
@if (enableCsvImports)
{
<div class="btn-group">
<button onclick="showAddUpgradeRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Upgrade Record")</button>
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('UpgradeRecord')">@translator.Translate(userLanguage, "Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('UpgradeRecord')">@translator.Translate(userLanguage, "Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTab()">@translator.Translate(userLanguage, "Print")</a></li>
<li><a class="dropdown-item" href="#" onclick="searchTableRows('upgrade-tab-pane')">@translator.Translate(userLanguage, "Search")</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="list-group-item">
<input class="btn-check" type="checkbox" id="chkSelectMode">
<label class="dropdown-item" for="chkSelectMode">@translator.Translate(userLanguage, "Select Mode")</label>
</div>
</li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='date' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Date" checked>
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Odometer" checked>
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='description' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Description" checked>
<label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='cost' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Cost" checked>
<label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='attachments' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Attachment">
<label class="form-check-label stretched-link" for="chkCol_Attachment">@translator.Translate(userLanguage, "Attachments")</label>
</div>
</li>
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='notes' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="chkCol_Notes" checked>
<label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
</div>
</li>
@foreach (string extraFieldColumn in extraFields)
{
var elementId = Guid.NewGuid();
<li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='@extraFieldColumn' onChange="showTableColumns(this, 'UpgradeRecord')" type="checkbox" id="@elementId">
<label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
</div>
</li>
}
</ul>
</div>
}
else
{
<button onclick="showAddUpgradeRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Upgrade Record")</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 flex-grow-1 col-xl-1 text-truncate" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 col-xl-4 text-truncate" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" onclick="toggleSort('upgrade-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@translator.Translate(userLanguage, "Attachments")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
<tbody>
@foreach (UpgradeRecord upgradeRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@upgradeRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditUpgradeRecordModal,@upgradeRecord.Id)" data-tags='@string.Join(" ", upgradeRecord.Tags)'>
<td class="col-2 flex-grow-1 col-xl-1 text-truncate" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(upgradeRecord.Date)">@upgradeRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(upgradeRecord.Mileage == default ? "---" : upgradeRecord.Mileage.ToString())</td>
<td class="col-3 flex-grow-1 flex-shrink-1 col-xl-4 text-truncate" data-column="description">@upgradeRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@(StaticHelper.HideZeroCost(upgradeRecord.Cost, hideZero))</td>
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="attachments">@await Html.PartialAsync("_AttachmentColumn", upgradeRecord.Files)</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = upgradeRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="upgradeRecordModal" tabindex="-1" role="dialog" aria-hidden="true" onpaste="handleModalPaste(event)">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="upgradeRecordModalContent">
</div>
</div>
</div>
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Select All")</span><i class="bi bi-check-square"></i></div></a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Deselect All")</span><i class="bi bi-x-square"></i></div></a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleRecords(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Edit Multiple")</span><i class="bi bi-pencil-square"></i></div></a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'UpgradeRecord', 'ServiceRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Service Records")</span><i class="bi bi-card-checklist"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'UpgradeRecord', 'RepairRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Repairs")</span><i class="bi bi-exclamation-octagon"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Duplicate To Vehicle")</span><i class="bi bi-copy"></i></div></a></li>
<li><a class="dropdown-item" href="#" onclick="insertOdometer(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Create Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="printTabStickers(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Print")</span><i class="bi bi-printer"></i></div></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Delete")</span><i class="bi bi-trash"></i></div></a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Statistics")</span><i class="bi bi-graph-up"></i></div></a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'UpgradeRecord')"><div class="d-flex justify-content-between"><span class="me-5">@translator.Translate(userLanguage, "Adjust Odometer")</span><i class="bi bi-speedometer"></i></div></a></li>
</ul>
@if (userColumnPreferences.Any())
{
@await Html.PartialAsync("_UserColumnPreferences", userColumnPreferences)
}

View File

@@ -0,0 +1,9 @@
@model List<UploadedFiles>
@if (Model.Any()){
<span class="position-relative">
<i class="bi bi-paperclip"></i>
<span class="position-absolute attachment-badge-xs start-100 translate-middle badge rounded-pill bg-secondary">
@Model.Count()
</span>
</span>
}

View File

@@ -0,0 +1,73 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model ImportMode
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage, "Import Data from CSV")</h5>
<button type="button" class="btn-close" onclick="hideBulkImportModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<div class="form-group">
<div class="col-12">
<div class="alert alert-warning" role="alert">
@translator.Translate(userLanguage, "In order for this utility to function properly, your CSV file MUST be formatted exactly like the provided sample. Dates must be supplied in a string. Numbers must be supplied as numbers without currency formatting.")
</div>
<div class="alert alert-danger" role="alert">
@translator.Translate(userLanguage, "Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.")
</div>
<a class="btn btn-link" href="@($"/Vehicle/GenerateCsvSample?mode={Model.ToString()}")" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<label for="csvFileUploader">@translator.Translate(userLanguage, "Upload CSV File")</label>
<input onChange="uploadFileAsync(this)" type="file" multiple accept=".csv" class="form-control-file" id="csvFileUploader">
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideBulkImportModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="importFromCsv()" class="btn btn-primary">@translator.Translate(userLanguage, "Import")</button>
</div>
<script>
var uploadedFile = "";
function importFromCsv() {
var vehicleId = GetVehicleId().vehicleId;
var mode = "@Model";
sloader.show();
$.post('/Vehicle/ImportToVehicleIdFromCsv', { vehicleId: vehicleId, mode: mode, fileName: uploadedFile }, function (data) {
sloader.hide();
if (data) {
successToast("Data Imported Successfully");
hideBulkImportModal();
if (mode == "GasRecord") {
getVehicleGasRecords(vehicleId);
} else if (mode == "ServiceRecord") {
getVehicleServiceRecords(vehicleId);
} else if (mode == "RepairRecord") {
getVehicleCollisionRecords(vehicleId);
} else if (mode == "TaxRecord") {
getVehicleTaxRecords(vehicleId);
} else if (mode == "UpgradeRecord") {
getVehicleUpgradeRecords(vehicleId);
} else if (mode == "SupplyRecord") {
getVehicleSupplyRecords(vehicleId);
} else if (mode == "PlanRecord") {
getVehiclePlanRecords(vehicleId);
} else if (mode == "OdometerRecord") {
getVehicleOdometerRecords(vehicleId);
}
} else {
errorToast(genericErrorMessage());
}
});
}
</script>

View File

@@ -0,0 +1,81 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<UserCollaborator>
<div class="row">
<div class="col-8">
<span class="lead">@translator.Translate(userLanguage, "Collaborators")</span>
</div>
<div class="col-4">
<button onclick="addCollaborator()" class="btn btn-link btn-sm"><i class="bi bi-person-add"></i></button>
</div>
</div>
<div class="row">
<table class="table table-hover">
<thead>
<tr class="d-flex">
<th scope="col" class="col-8">@translator.Translate(userLanguage, "Username")</th>
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody>
@foreach (UserCollaborator user in Model)
{
<tr class="d-flex">
<td class="col-8">@user.UserName</td>
<td class="col-4">
@if(User.Identity.Name != user.UserName)
{
<button onclick="deleteCollaborator(@user.UserVehicle.UserId, @user.UserVehicle.VehicleId)" class="btn btn-outline-danger btn-sm"><i class="bi bi-trash"></i></button>
}
</td>
</tr>
}
</tbody>
</table>
</div>
<script>
function deleteCollaborator(userId, vehicleId) {
$.post('/Vehicle/DeleteCollaboratorFromVehicle', {userId: userId, vehicleId: vehicleId}, function(data){
if (data) {
successToast('Collaborator Removed');
refreshCollaborators();
} else {
errorToast(genericErrorMessage());
}
})
}
function addCollaborator() {
Swal.fire({
title: 'Add Collaborator',
html: `
<input type="text" id="inputUserName" class="swal2-input" placeholder="Username" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Add',
focusConfirm: false,
preConfirm: () => {
const userName = $("#inputUserName").val();
if (!userName) {
Swal.showValidationMessage(`Please enter a username`);
}
return { userName }
},
}).then(function (result) {
if (result.isConfirmed) {
var vehicleId = GetVehicleId().vehicleId;
$.post('/Vehicle/AddCollaboratorsToVehicle', { username: result.value.userName, vehicleId: vehicleId }, function (data) {
if (data.success) {
successToast(data.message);
refreshCollaborators();
} else {
errorToast(data.message)
}
});
}
});
}
</script>

View File

@@ -0,0 +1,96 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model CostDistanceTableForVehicle
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var hideZero = userConfig.HideZero;
var years = Model.CostData.Select(x => x.Year).OrderByDescending(x => x).Distinct();
var months = Model.CostData.OrderBy(x => x.MonthId).Select(x => x.MonthName).Distinct();
}
@if (Model.CostData.Any())
{
<div>
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "Vehicle Monthly Cost Breakdown"))</h5>
<button type="button" class="btn-close" onclick="hideDataTable()" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12" style="overflow-x:auto;">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" style="cursor:pointer;" onclick="toggleBarChartTableData()" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@(translator.Translate(userLanguage, "Year"))</th>
@foreach(int year in years){
if (year != default){
<th scope="col" style="cursor:pointer;" onclick="toggleBarChartTableData()" class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@year</th>
}
}
</tr>
</thead>
<tbody>
@foreach(string month in months){
<tr class="d-flex">
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@month</td>
@foreach(int year in years){
if (year != default){
{
var dataToDisplay = Model.CostData.Where(x => x.Year == year && x.MonthName == month).FirstOrDefault();
if (dataToDisplay != null && dataToDisplay != default)
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(dataToDisplay.Cost.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(dataToDisplay.DistanceTraveled != default ? $"{dataToDisplay.DistanceTraveled.ToString("N0")} {Model.DistanceUnit}" : "---")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(dataToDisplay.CostPerDistanceTraveled.ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
} else {
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@("---")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}")}")</td>
}
}
}
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex fw-bold">
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate">@(translator.Translate(userLanguage, "Total"))</td>
@foreach (int year in years)
{
if (year != default)
{
{
var yearDataToDisplay = Model.CostData.Where(x => x.Year == year);
if (yearDataToDisplay != null && yearDataToDisplay != default)
{
var distanceTraveled = yearDataToDisplay.Sum(x => x.DistanceTraveled);
var costAccrued = yearDataToDisplay.Sum(x => x.Cost);
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(costAccrued.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(distanceTraveled != default ? $"{distanceTraveled.ToString("N0")} {Model.DistanceUnit}" : "---")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(distanceTraveled != default && costAccrued != default ? (costAccrued / distanceTraveled).ToString("C2") : 0M.ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td>
}
else
{
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@("---")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@($"{StaticHelper.HideZeroCost(0M.ToString("C2"), hideZero)}")</td>
}
}
}
}
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
}
else
{
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.")</h4>
</div>
}

View File

@@ -0,0 +1,91 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model CostMakeUpForVehicle
@if (Model.CollisionRecordSum + Model.ServiceRecordSum + Model.GasRecordSum + Model.TaxRecordSum + Model.UpgradeRecordSum > 0)
{
<canvas id="pie-chart"></canvas>
<script>
function getCostMakeUpChart() {
let costMakeUpChartLabels = [];
let costMakeUpChartData = [];
availableMetrics.map(x=> {
switch(x) {
case 'ServiceRecord':
costMakeUpChartLabels.push(decodeHTMLEntities('@translator.Translate(userLanguage, "Service Records")'));
costMakeUpChartData.push(globalParseFloat('@Model.ServiceRecordSum'));
break;
case 'RepairRecord':
costMakeUpChartLabels.push(decodeHTMLEntities('@translator.Translate(userLanguage, "Repairs")'));
costMakeUpChartData.push(globalParseFloat('@Model.CollisionRecordSum'));
break;
case 'UpgradeRecord':
costMakeUpChartLabels.push(decodeHTMLEntities('@translator.Translate(userLanguage, "Upgrades")'));
costMakeUpChartData.push(globalParseFloat('@Model.UpgradeRecordSum'));
break;
case 'GasRecord':
costMakeUpChartLabels.push(decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel")'));
costMakeUpChartData.push(globalParseFloat('@Model.GasRecordSum'));
break;
case 'TaxRecord':
costMakeUpChartLabels.push(decodeHTMLEntities('@translator.Translate(userLanguage, "Tax")'));
costMakeUpChartData.push(globalParseFloat('@Model.TaxRecordSum'));
break;
}
})
return {
labels: costMakeUpChartLabels,
data: costMakeUpChartData
}
}
renderChart();
function renderChart() {
let chartData = getCostMakeUpChart();
var useDarkMode = getGlobalConfig().useDarkMode;
new Chart($("#pie-chart"), {
type: 'pie',
data: {
labels: chartData.labels,
datasets: [
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Type")'),
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ffa600", "#ff6361" ],
data: chartData.data
}
]
},
options: {
onClick: (e, a, c) => {
showDataTable(a);
},
onHover: (e, a, c) => {
a.length > 0 ? c.canvas.style.cursor = 'pointer' : c.canvas.style.cursor = 'default';
},
plugins: {
legend: {
position: "bottom",
labels: {
color: useDarkMode ? "#fff" : "#000"
}
},
title: {
display: true,
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Type")'),
color: useDarkMode ? "#fff" : "#000"
},
}
}
});
}
</script>
}
else
{
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.")</h4>
</div>
}

View File

@@ -0,0 +1,80 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var hideZero = userConfig.HideZero;
}
@model CostTableForVehicle
@if (Model.CollisionRecordSum + Model.ServiceRecordSum + Model.GasRecordSum + Model.TaxRecordSum + Model.UpgradeRecordSum > 0)
{
<div>
<div class="modal-header">
<h5 class="modal-title">@(translator.Translate(userLanguage, "Vehicle Cost Breakdown"))</h5>
<button type="button" class="btn-close" onclick="hideDataTable()" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Type")</th>
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")<span class="cost-table-hint d-none">@($"({Model.NumberOfDays.ToString("N0")})")</span></th>
<th scope="col" style="cursor:pointer;" onclick="toggleCostTableHint()" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)<span class="cost-table-hint d-none">@($"({Model.TotalDistance.ToString("N0")})")</span></th>
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</th>
</tr>
</thead>
<tbody>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Service Records")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.ServiceRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Repairs")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.CollisionRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Upgrades")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.UpgradeRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Fuel")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.GasRecordSum.ToString("C2"), hideZero))</td>
</tr>
<tr class="d-flex">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Taxes")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TaxRecordSum.ToString("C2"), hideZero))</td>
</tr>
</tbody>
<tfoot>
<tr class="d-flex fw-bold">
<td class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalPerDay.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalPerMile.ToString("C2"), hideZero))</td>
<td class="col-3 flex-grow-1">@(StaticHelper.HideZeroCost(Model.TotalCost.ToString("C2"), hideZero))</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
}
else
{
<div class="text-center">
<h4>@translator.Translate(userLanguage, "No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.")</h4>
</div>
}

View File

@@ -0,0 +1,42 @@
@model List<ExtraField>
@if (Model.Any()){
@foreach (ExtraField field in Model)
{
var elementId = Guid.NewGuid();
<div class="extra-field">
<label for="@elementId">@field.Name</label>
@switch(field.FieldType){
case (ExtraFieldType.Text):
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
break;
case (ExtraFieldType.Number):
<input type="number" inputmode="numeric" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
break;
case (ExtraFieldType.Decimal):
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
break;
case (ExtraFieldType.Date):
<div class="input-group">
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<script>initExtraFieldDatePicker('@elementId')</script>
break;
case (ExtraFieldType.Time):
<input type="time" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
break;
case (ExtraFieldType.Location):
<div class="input-group">
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="populateLocationField('@elementId')"><i class="bi bi-geo-alt"></i></button>
</div>
</div>
break;
default:
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
break;
}
</div>
}
}

View File

@@ -0,0 +1,49 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<ExtraField>
@if (Model.Any()){
@foreach (ExtraField field in Model)
{
var elementId = Guid.NewGuid();
<div class="extra-field">
<label for="@elementId">@field.Name</label>
@switch(field.FieldType){
case (ExtraFieldType.Text):
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
break;
case (ExtraFieldType.Number):
<input type="number" inputmode="numeric" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
break;
case (ExtraFieldType.Decimal):
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
break;
case (ExtraFieldType.Date):
<div class="input-group">
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<script>initExtraFieldDatePicker('@elementId')</script>
break;
case (ExtraFieldType.Time):
<input type="time" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
break;
case (ExtraFieldType.Location):
<div class="input-group">
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="populateLocationField('@elementId')"><i class="bi bi-geo-alt"></i></button>
</div>
</div>
break;
default:
<input type="text" id="@elementId" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
break;
}
</div>
}
}

View File

@@ -0,0 +1,20 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var uploaderId = Guid.NewGuid();
}
@model bool
<label for="@uploaderId">@translator.Translate(userLanguage, Model ? "Upload more documents" : "Upload documents(optional)")</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="d-none" id="@uploaderId">
<div class="motovaultpro-uploader d-flex justify-content-center align-items-center align-content-center" onclick="toggleUploadFileBrowser(this)" ondragover="handlePotentialFileDrop(event)" ondragleave="handleNoFileDrop(event)" ondrop="handleEndFileDrop(event)">
<div class="motovaultpro-link-uploader"><button class="btn btn-sm btn-primary" type="button" onclick="uploadVehicleLinksAsync(event)">@translator.Translate(userLanguage, "Attach Link")</button></div>
<span class="text-center"><span class="lead"><i class="bi bi-upload"></i></span><br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small></span>
</div>
<script>
function getUploaderId(){
return {uploaderId: '@uploaderId'};
}
</script>

View File

@@ -0,0 +1,25 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<UploadedFiles>
<label id="documentsPendingUploadLabel">@translator.Translate(userLanguage, "Documents Pending Upload")</label>
<ul class="list-group" id="documentsPendingUploadList">
@foreach (UploadedFiles filesUploaded in Model)
{
<li class="list-group-item">
<div class="d-flex justify-content-between">
<a class="uploadedFileName d-flex align-items-center text-truncate" href="@filesUploaded.Location" title="@filesUploaded.Name" target="_blank">
<span class="lead me-2"><i class="bi @StaticHelper.GetIconByFileExtension(filesUploaded.Location)"></i></span><span class="text-link">@filesUploaded.Name</span>
</a>
<div class="d-flex align-items-center">
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
</div>
</div>
</li>
}
</ul>

View File

@@ -0,0 +1,103 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<CostForVehicleByMonth>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var barGraphColors = StaticHelper.GetBarChartColors();
var sortedByMPG = Model.OrderBy(x => x.Cost).ToList();
}
@if (Model.Any(x=>x.Cost > 0) || Model.Any(x=>x.DistanceTraveled > 0))
{
<canvas id="bar-chart"></canvas>
<script>
renderChart();
function renderChart() {
var barGraphLabels = [];
var barGraphData = [];
var lineChartData = [];
//color gradient from high to low
var barGraphColors = [];
var useDarkMode = getGlobalConfig().useDarkMode;
@foreach (CostForVehicleByMonth gasCost in Model)
{
@:barGraphLabels.push(decodeHTMLEntities("@gasCost.MonthName"));
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
@:lineChartData.push(globalParseFloat('@gasCost.DistanceTraveled'));
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
@:barGraphColors.push('@barGraphColors[index]');
}
new Chart($("#bar-chart"), {
type: 'bar',
data: {
labels: barGraphLabels,
datasets: [
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses by Month")'),
backgroundColor: barGraphColors,
data: barGraphData,
order: 1,
yAxisID: 'y',
},
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Distance Traveled by Month")'),
data: lineChartData,
borderColor: useDarkMode ? "#fff" : "#000",
backgroundColor: useDarkMode ? "#000" : "#fff",
type: 'line',
order: 0,
yAxisID: 'y1',
}
]
},
options: {
onClick: (e, a, c) => {
showBarChartTable(a);
},
onHover: (e, a, c) => {
a.length > 0 ? c.canvas.style.cursor = 'pointer' : c.canvas.style.cursor = 'default';
},
plugins: {
title: {
display: true,
color: useDarkMode ? "#fff" : "#000",
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Expenses and Distance Traveled by Month")')
},
legend: {
display: false,
labels: {
color: useDarkMode ? "#fff" : "#000"
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
color: useDarkMode ? "#fff" : "#000"
}
},
y1: {
beginAtZero: true,
position: 'right',
ticks: {
color: useDarkMode ? "#fff" : "#000"
}
},
x: {
ticks: {
color: useDarkMode ? "#fff" : "#000"
}
}
}
}
});
}
</script>
} else
{
<div class="text-center">
<h4>@translator.Translate(userLanguage,"No data found, insert/select some data to see visualizations here.")</h4>
</div>
}

View File

@@ -0,0 +1,56 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model GenericRecordEditModel
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage,"Edit Multiple Records")</h5>
<button type="button" class="btn-close" onclick="hideGenericRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<label for="genericRecordDate">@translator.Translate(userLanguage,"Date")</label>
<div class="input-group">
<input type="text" id="genericRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="genericRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
<input type="number" inputmode="numeric" id="genericRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="genericRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="genericRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="genericRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="genericRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
<label for="genericRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
<select multiple class="form-select" id="genericRecordTag"></select>
@await Html.PartialAsync("_ExtraFieldMultiple", Model.EditRecord.ExtraFields)
</div>
<div class="col-md-6 col-12">
<label for="genericRecordNotes">@translator.Translate(userLanguage, "Notes(use --- to clear all existing notes)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="genericRecordNotes" class="form-control" rows="5"></textarea>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideGenericRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="saveGenericRecord()">@translator.Translate(userLanguage,"Edit")</button>
</div>
<script>
var recordsToEdit = [];
@foreach(int recordId in Model.RecordIds)
{
@:recordsToEdit.push(@recordId);
}
function getGenericRecordEditModelData(){
return {
dataType: decodeHTMLEntities('@Model.DataType.ToString()')
}
}
</script>

View File

@@ -0,0 +1,34 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<SearchResult>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div style="max-height:50vh; overflow-y:auto;">
@if (Model.Any())
{
@foreach (SearchResult result in Model)
{
<div class="row border p-2 m-1" onclick="loadGlobalSearchResult(@result.Id, '@result.RecordType')" style="cursor:pointer;">
<div class="col-1">
<i class="bi @StaticHelper.GetImportModeIcon(result.RecordType)"></i>
</div>
<div class="col-11">
@result.Description
</div>
</div>
}
} else
{
<div class="row border p-2 m-1">
<div class="col-1">
<i class="bi bi-ban"></i>
</div>
<div class="col-11">
@translator.Translate(userLanguage, "No Data Found")
</div>
</div>
}
</div>

View File

@@ -0,0 +1,101 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model MPGForVehicleByMonth
@{
var barGraphColors = StaticHelper.GetBarChartColors();
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@if (Model.CostData.Any(x => x.Cost > 0))
{
var chartMax = Math.Ceiling(Model.CostData.Max(x => x.Cost));
var graphGrace = Decimal.ToInt32(Model.CostData.Max(x => x.Cost) - Model.CostData.Min(x => x.Cost));
var chartMin = Math.Floor(Model.CostData.Min(x => x.Cost) - graphGrace);
if (graphGrace < 0 || chartMin < 0)
{
graphGrace = 0;
chartMin = 0;
}
var stepSize = StaticHelper.CalculateNiceStepSize(chartMin, chartMax, 8);
var remMin = stepSize > 0 ? chartMin % stepSize : 0;
var cleanedMin = chartMin - remMin;
if (remMin >= (stepSize / 2))
{
cleanedMin += stepSize;
}
var remMax = stepSize > 0 ? chartMax % stepSize : 0;
var cleanedMax = chartMax - remMax;
if (remMax >= (stepSize / 2))
{
cleanedMax += stepSize;
}
<canvas id="bar-chart-mpg"></canvas>
<script>
renderChart();
function renderChart() {
var barGraphLabels = [];
var barGraphData = [];
//color gradient from high to low
var barGraphColors = [];
var useDarkMode = getGlobalConfig().useDarkMode;
@foreach (CostForVehicleByMonth gasCost in Model.CostData)
{
@:barGraphLabels.push(decodeHTMLEntities("@gasCost.MonthName"));
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
var index = Model.SortedCostData.FindIndex(x => x.MonthName == gasCost.MonthName);
@:barGraphColors.push('@barGraphColors[index]');
}
new Chart($("#bar-chart-mpg"), {
type: 'bar',
data: {
labels: barGraphLabels,
datasets: [
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Fuel Mileage by Month")'),
backgroundColor: barGraphColors,
data: barGraphData
}
]
},
options: {
plugins: {
title: {
display: true,
color: useDarkMode ? "#fff" : "#000",
text: decodeHTMLEntities('@($"{translator.Translate(userLanguage, "Fuel Mileage by Month")}({Model.Unit})")')
},
legend: {
display: false,
labels: {
color: useDarkMode ? "#fff" : "#000"
}
}
},
scales: {
y: {
beginAtZero: false,
max: Math.ceil(decodeHTMLEntities('@(cleanedMax)')),
min: Math.floor(decodeHTMLEntities('@(cleanedMin)')),
ticks: {
color: useDarkMode ? "#fff" : "#000",
stepSize: Math.ceil(decodeHTMLEntities('@(stepSize)'))
}
},
x: {
ticks: {
color: useDarkMode ? "#fff" : "#000"
}
}
}
}
});
}
</script>
}
else
{
<div class="text-center">
<h4>@translator.Translate(userLanguage,"No data found, insert/select some data to see visualizations here.")</h4>
</div>
}

View File

@@ -0,0 +1,69 @@
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@using MotoVaultPro.Helper
@model List<ReminderRecordViewModel>
@if (Model.Count() > 1)
{
<div class="mb-2">
<input class="form-check-input" type="checkbox" onchange="showMultipleRemindersSelector()" id="multipleRemindersCheck">
<label class="form-check-label" for="multipleRemindersCheck">@translator.Translate(userLanguage, "Multiple")</label>
</div>
}
<select class="form-select" id="recurringReminderInput">
@if (Model.Any())
{
@foreach (ReminderRecordViewModel reminderRecord in Model)
{
@switch(reminderRecord.UserMetric){
case (ReminderMetric.Both):
<!option value="@reminderRecord.Id" data-description="@reminderRecord.Description" class="@StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
@($"{reminderRecord.Description} | {reminderRecord.Date.ToShortDateString()} | {reminderRecord.Mileage}")
</!option>
break;
case (ReminderMetric.Odometer):
<!option value="@reminderRecord.Id" data-description="@reminderRecord.Description" class="@StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
@($"{reminderRecord.Description} | {reminderRecord.Mileage}")
</!option>
break;
case (ReminderMetric.Date):
<!option value="@reminderRecord.Id" data-description="@reminderRecord.Description" class="@StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
@($"{reminderRecord.Description} | {reminderRecord.Date.ToShortDateString()}")
</!option>
break;
}
}
} else
{
<!option value="0">@translator.Translate(userLanguage, "No Recurring Reminders Found")</!option>
}
</select>
<div id="recurringMultipleReminders" style="display:none;">
<ul class="list-group">
@foreach (ReminderRecordViewModel reminderRecord in Model)
{
<li class="list-group-item text-start">
<input class="form-check-input" type="checkbox" value="@reminderRecord.Id" data-description="@reminderRecord.Description" id="recurringReminder_@reminderRecord.Id">
<label class="form-check-label stretched-link" for="recurringReminder_@reminderRecord.Id">
@reminderRecord.Description
<br /><small class="badge @StaticHelper.GetReminderUrgencyColor(reminderRecord.Urgency)">
@switch (reminderRecord.UserMetric){
case (ReminderMetric.Both):
<i class='bi bi-calendar-event me-2'></i>@reminderRecord.Date.ToShortDateString()<i class='bi bi-speedometer ms-2 me-2'></i>@reminderRecord.Mileage
break;
case (ReminderMetric.Odometer):
<i class='bi bi-speedometer me-2'></i>@reminderRecord.Mileage
break;
case (ReminderMetric.Date):
<i class='bi bi-calendar-event me-2'></i>@reminderRecord.Date.ToShortDateString()
break;
}
</small>
</label>
</li>
}
</ul>
</div>

View File

@@ -0,0 +1,62 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model ReminderMakeUpForVehicle
@if (Model.UrgentCount + Model.VeryUrgentCount + Model.NotUrgentCount + Model.PastDueCount > 0)
{
<canvas id="donut-chart"></canvas>
<script>
renderChart();
function renderChart() {
var useDarkMode = getGlobalConfig().useDarkMode;
new Chart($("#donut-chart"), {
type: 'doughnut',
data: {
labels: [
decodeHTMLEntities('@translator.Translate(userLanguage, "Not Urgent")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Urgent")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Very Urgent")'),
decodeHTMLEntities('@translator.Translate(userLanguage, "Past Due")')
],
datasets: [
{
label: decodeHTMLEntities('@translator.Translate(userLanguage, "Reminders by Category")'),
backgroundColor: ["#488f31", "#ffa600", "#de425b", "#cccccc"],
data: [
@Model.NotUrgentCount,
@Model.UrgentCount,
@Model.VeryUrgentCount,
@Model.PastDueCount
]
}
]
},
options: {
plugins: {
legend: {
position: "bottom",
labels: {
color: useDarkMode ? "#fff" : "#000"
}
},
title: {
display: true,
text: decodeHTMLEntities('@translator.Translate(userLanguage, "Reminders by Urgency")'),
color: useDarkMode ? "#fff" : "#000"
},
}
}
});
}
</script>
}
else
{
<div class="text-center">
<h4>@translator.Translate(userLanguage,"No data found, create reminders to see visualizations here.")</h4>
</div>
}

View File

@@ -0,0 +1,168 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model ReportViewModel
<script>
var availableMetrics = [];
@foreach (ImportMode importMode in Model.AvailableMetrics)
{
@:availableMetrics.push('@importMode');
}
</script>
<div class="container reportTabContainer">
<div class="row hideOnPrint" id="reportHeaderContent">
@await Html.PartialAsync("_ReportHeader", Model.ReportHeaderForVehicle)
</div>
<hr />
<div class="row hideOnPrint">
<div class="col-md-3 col-12 mt-2">
<div class="row">
<div class="col-12">
<select class="form-select" id="yearOption" onchange="yearUpdated()">
<option value="0">@translator.Translate(userLanguage, "All Time")</option>
@foreach (int year in Model.Years)
{
<option value="@year">@year</option>
}
</select>
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="costMakeUpReportContent">
@await Html.PartialAsync("_CostMakeUpReport", Model.CostMakeUpForVehicle)
</div>
</div>
</div>
<div class="col-md-6 col-12 mt-2">
<div class="row">
<div class="col-md-1 d-sm-none d-md-block"></div>
<div class="col-12 col-md-10">
<div class="dropdown d-grid dropdown-center">
<button class="btn btn-outline-warning dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" @(Model.AvailableMetrics.Any() ? "" : "disabled")>
@translator.Translate(userLanguage, "Metrics")
</button>
<ul class="dropdown-menu" style="width:100%;">
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input" onChange="updateCheckAll()" type="checkbox" id="selectAllExpenseCheck" value="0" checked>
<label class="form-check-label stretched-link" for="selectAllExpenseCheck">@translator.Translate(userLanguage, "Select All")</label>
</div>
</li>
<li class="dropdown-item @(Model.AvailableMetrics.Contains(ImportMode.OdometerRecord) ? "" : "d-none")">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="odometerExpenseCheck" value="6" checked>
<label class="form-check-label stretched-link" for="odometerExpenseCheck">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item @(Model.AvailableMetrics.Contains(ImportMode.ServiceRecord) ? "" : "d-none")">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="serviceExpenseCheck" value="1" checked>
<label class="form-check-label stretched-link" for="serviceExpenseCheck">@translator.Translate(userLanguage, "Service")</label>
</div>
</li>
<li class="dropdown-item @(Model.AvailableMetrics.Contains(ImportMode.RepairRecord) ? "" : "d-none")">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="repairExpenseCheck" value="2" checked>
<label class="form-check-label stretched-link" for="repairExpenseCheck">@translator.Translate(userLanguage, "Repairs")</label>
</div>
</li>
<li class="dropdown-item @(Model.AvailableMetrics.Contains(ImportMode.UpgradeRecord) ? "" : "d-none")">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="upgradeExpenseCheck" value="3" checked>
<label class="form-check-label stretched-link" for="upgradeExpenseCheck">@translator.Translate(userLanguage, "Upgrades")</label>
</div>
</li>
<li class="dropdown-item @(Model.AvailableMetrics.Contains(ImportMode.GasRecord) ? "" : "d-none")">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="gasExpenseCheck" value="4" checked>
<label class="form-check-label stretched-link" for="gasExpenseCheck">@translator.Translate(userLanguage, "Fuel")</label>
</div>
</li>
<li class="dropdown-item @(Model.AvailableMetrics.Contains(ImportMode.TaxRecord) ? "" : "d-none")">
<div class="list-group-item">
<input class="form-check-input reportCheckBox" onChange="updateCheck()" type="checkbox" id="taxExpenseCheck" value="5" checked>
<label class="form-check-label stretched-link" for="taxExpenseCheck">@translator.Translate(userLanguage, "Taxes")</label>
</div>
</li>
</ul>
</div>
</div>
<div class="col-md-1 d-sm-none d-md-block"></div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="gasCostByMonthReportContent">
@await Html.PartialAsync("_GasCostByMonthReport", Model.CostForVehicleByMonth)
</div>
</div>
</div>
<div class="col-md-3 col-12 mt-2">
<div class="row">
<div class="col-12">
<select class="form-select" onchange="updateReminderPie()" id="reminderOption">
<option value="0">@translator.Translate(userLanguage, "As of Today")</option>
<option value="30">@translator.Translate(userLanguage, "+30 Days")</option>
<option value="60">@translator.Translate(userLanguage, "+60 Days")</option>
<option value="90">@translator.Translate(userLanguage, "+90 Days")</option>
</select>
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="reminderMakeUpReportContent">
@await Html.PartialAsync("_ReminderMakeUpReport", Model.ReminderMakeUpForVehicle)
</div>
</div>
</div>
</div>
<hr />
<div class="row hideOnPrint">
<div class="col-md-3 col-12 chartContainer" id="collaboratorContent">
@await Html.PartialAsync("_Collaborators", Model.Collaborators)
</div>
<div class="col-md-6 col-12 chartContainer">
<div class="d-flex justify-content-center align-items-center col-12 chartContainer" id="monthFuelMileageReportContent">
@await Html.PartialAsync("_MPGByMonthReport", Model.FuelMileageForVehicleByMonth)
</div>
</div>
<div class="col-md-3 col-12 chartContainer">
<div class="d-grid">
<button onclick="showGlobalSearch()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Search")<i class="bi ms-2 bi-search"></i></button>
</div>
<div class="d-grid">
<button onclick="generateVehicleHistoryReport()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Vehicle Maintenance Report")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
<div class="d-grid">
<button onclick="exportAttachments()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Export Attachments")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
@if (Model.CustomWidgetsConfigured)
{
<div class="d-grid">
<button onclick="loadCustomWidgets()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Additional Widgets")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
}
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="vehicleDataTableModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="vehicleDataTableModalContent">
</div>
</div>
</div>
@if (Model.CustomWidgetsConfigured)
{
<div class="modal fade" data-bs-focus="false" id="vehicleCustomWidgetsModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="vehicleCustomWidgetsModalContent">
</div>
</div>
</div>
}
<script>
getSelectedMetrics();
</script>

View File

@@ -0,0 +1,24 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model ReportHeader
<div class="col-md-3 col-12 mt-2 text-center">
<span class="lead">@Model.MaxOdometer.ToString("N0")</span><br />
<span class="text-secondary">@translator.Translate(userLanguage, "Last Reported Odometer Reading")</span>
</div>
<div class="col-md-3 col-12 mt-2 text-center">
<span class="lead">@Model.DistanceTraveled.ToString("N0")</span><br />
<span class="text-secondary">@translator.Translate(userLanguage, "Distance Traveled")</span>
</div>
<div class="col-md-3 col-12 mt-2 text-center">
<span class="lead">@StaticHelper.HideZeroCost(Model.TotalCost.ToString("C2"), true)</span><br />
<span class="text-secondary">@translator.Translate(userLanguage, "Total Cost")</span>
</div>
<div class="col-md-3 col-12 mt-2 text-center">
<span class="lead">@Model.AverageMPG</span><br />
<span class="text-secondary">@translator.Translate(userLanguage, "Average Fuel Economy")</span>
</div>

View File

@@ -0,0 +1,63 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model ReportParameter
<h2 class="swal2-title mb-2">@translator.Translate(userLanguage, "Select Columns")</h2>
<div id="columnSelector" style="max-height:50vh; overflow-y:auto;">
<ul class="list-group">
@foreach (string column in Model.VisibleColumns)
{
<li class="list-group-item text-start">
<input class="form-check-input column-default" type="checkbox" value="@column" id="visibleColumn_@column" checked>
<label class="form-check-label stretched-link" for="visibleColumn_@column">@(translator.Translate(userLanguage, column == nameof(GenericReportModel.DataType) ? "Type" : column))</label>
</li>
}
@foreach (string extraField in Model.ExtraFields)
{
<li class="list-group-item text-start">
<input class="form-check-input column-extrafield" type="checkbox" value="@extraField" id="extraField_@extraField">
<label class="form-check-label stretched-link" for="extraField_@extraField">@extraField</label>
</li>
}
</ul>
</div>
<div class="mt-2 mb-2">
<ul class="list-group">
<li class="list-group-item text-start">
<input class="form-check-input" type="checkbox" role="switch" id="printIndividualRecordsCheck">
<label class="form-check-label" for="printIndividualRecordsCheck">@translator.Translate(userLanguage, "Print Individual Records")</label>
</li>
</ul>
</div>
<div class="mt-2 mb-2">
<ul class="list-group">
<li class="list-group-item text-center" style="cursor:pointer;" onclick="showReportAdvancedParameters()">
@translator.Translate(userLanguage, "Advanced Filters")
</li>
</ul>
</div>
<h2 class="mb-2 report-advanced-parameters d-none">@translator.Translate(userLanguage, "Filter by Tags")</h2>
<div class="text-start report-advanced-parameters d-none">
<select class="form-select mb-2" id="tagSelector">
<!option value="@TagFilter.Exclude">@translator.Translate(userLanguage, "Exclude Records with these Tags")</!option>
<!option value="@TagFilter.IncludeOnly">@translator.Translate(userLanguage, "Only Include Records with these Tags")</!option>
</select>
<select multiple id="tagSelectorInput"></select>
</div>
<h2 class="mb-2 report-advanced-parameters d-none">@translator.Translate(userLanguage, "Filter by Date Range")</h2>
<div class="text-start report-advanced-parameters d-none">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="dateRangeSelector">
<label class="form-check-label" for="dateRangeSelector">@translator.Translate(userLanguage, "Filter by Date Range")</label>
</div>
<div class="input-group">
<span class="input-group-text">@translator.Translate(userLanguage, "From")</span>
<input type="text" id="dateRangeStartDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Start Date")">
<span class="input-group-text">@translator.Translate(userLanguage, "To")</span>
<input type="text" id="dateRangeEndDate" class="form-control" placeholder="@translator.Translate(userLanguage,"End Date")">
</div>
</div>

View File

@@ -0,0 +1,2 @@
@model string
@Html.Raw(Model)

View File

@@ -0,0 +1,277 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model StickerViewModel
@{
var userConfig = config.GetUserConfig(User);
var hideZero = userConfig.HideZero;
var userLanguage = userConfig.UserLanguage;
}
@if( Model.ReminderRecords.Any()){
@foreach(ReminderRecord reminder in Model.ReminderRecords){
<div class="reminderSticker">
<div class="row justify-content-center mt-2">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo-sticker" />
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-1">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2 text-uppercase fw-bold">@($"{reminder.Description}")</p>
</div>
</div>
@if (reminder.Metric == ReminderMetric.Odometer || reminder.Metric == ReminderMetric.Both)
{
<div class="row">
<div class="col-12 text-center">
<p class="display-2">@($"{translator.Translate(userLanguage, "Odometer")}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2 fw-bold">@($"{reminder.Mileage}")</p>
</div>
</div>
}
@if (reminder.Metric == ReminderMetric.Date || reminder.Metric == ReminderMetric.Both)
{
<div class="row">
<div class="col-12 text-center">
<p class="display-2">@($"{translator.Translate(userLanguage, "Date")}")</p>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<p class="display-2 fw-bold">@($"{reminder.Date.ToShortDateString()}")</p>
</div>
</div>
}
@if (reminder.Metric == ReminderMetric.Both)
{
<div class="row">
<div class="col-12 text-center">
<p class="display-2 text-uppercase">@($"{translator.Translate(userLanguage, "Whichever comes first")}")</p>
</div>
</div>
}
</div>
}
} else if (Model.GenericRecords.Any()){
@foreach(GenericRecord genericRecord in Model.GenericRecords){
<div class="d-flex flex-column recordSticker">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
<hr />
<div class="row">
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
<li class="list-group-item">
<span class="lead">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</span>
</li>
@foreach (ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
</ul>
</div>
<div class="col-6">
<ul class="list-group">
@if(!string.IsNullOrWhiteSpace(genericRecord.Description)){
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {genericRecord.Description}")
</li>
}
@switch(Model.RecordType){
case ImportMode.ServiceRecord:
case ImportMode.RepairRecord:
case ImportMode.UpgradeRecord:
case ImportMode.GasRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Odometer")}: {genericRecord.Mileage}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {genericRecord.Cost.ToString("C")}")
</li>
break;
case ImportMode.TaxRecord:
case ImportMode.PlanRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {genericRecord.Cost.ToString("C")}")
</li>
break;
case ImportMode.OdometerRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Odometer")}: {genericRecord.Mileage}")
</li>
break;
}
@foreach(ExtraField extraField in genericRecord.ExtraFields){
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
</div>
<hr />
@if(genericRecord.RequisitionHistory.Any()){
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Quantity")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Cost")</th>
</tr>
</thead>
<tbody>
@foreach (SupplyUsageHistory usageHistory in genericRecord.RequisitionHistory)
{
<tr class="d-flex">
<td class="col-2 text-truncate">@usageHistory.PartNumber</td>
<td class="col-6 text-truncate">@usageHistory.Description</td>
<td class="col-2">@usageHistory.Quantity.ToString("F")</td>
<td class="col-2">@usageHistory.Cost.ToString("C2")</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<hr />
}
<div class="row flex-grow-1 flex-shrink-1">
<div class="col-12">
<div class="stickerNote ms-1 me-1 p-1">
@(genericRecord.Notes)
</div>
</div>
</div>
</div>
}
<script>setMarkDownStickerNotes()</script>
} else if (Model.SupplyRecords.Any()){
@foreach (SupplyRecord supplyRecord in Model.SupplyRecords)
{
<div class="d-flex flex-column recordSticker">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
<hr />
<div class="row">
@if(Model.VehicleData.Id != default){
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
<li class="list-group-item">
<span class="lead">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</span>
</li>
@foreach (ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
</ul>
</div>
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {supplyRecord.Description}")
</li>
@if(!string.IsNullOrWhiteSpace(supplyRecord.PartNumber)){
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Part Number")}: {supplyRecord.PartNumber}")
</li>
}
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Supplier/Vendor")}: {supplyRecord.PartSupplier}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {supplyRecord.Cost.ToString("C")}")
</li>
@foreach (ExtraField extraField in supplyRecord.ExtraFields)
{
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
} else {
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {supplyRecord.Description}")
</li>
@if (!string.IsNullOrWhiteSpace(supplyRecord.PartNumber))
{
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Part Number")}: {supplyRecord.PartNumber}")
</li>
}
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Supplier/Vendor")}: {supplyRecord.PartSupplier}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {supplyRecord.Cost.ToString("C")}")
</li>
</ul>
</div>
<div class="col-6">
<ul class="list-group">
@foreach (ExtraField extraField in supplyRecord.ExtraFields)
{
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
}
</div>
<hr />
<div class="row flex-grow-1 flex-shrink-1">
<div class="col-12">
<div class="stickerNote ms-1 me-1 p-1">
@(supplyRecord.Notes)
</div>
</div>
</div>
</div>
}
<script>setMarkDownStickerNotes()</script>
}

View File

@@ -0,0 +1,35 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<UploadedFiles>
@if (Model.Any())
{
<label id="uploadedDocumentsLabel">@translator.Translate(userLanguage, "Uploaded Documents")</label>
<ul class="list-group" id="uploadedDocumentsList">
@foreach (UploadedFiles filesUploaded in Model)
{
<li class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<a class="uploadedFileName d-flex align-items-center text-truncate" href="@filesUploaded.Location" title="@filesUploaded.Name" target="_blank">
<span class="lead me-2"><i class="bi @StaticHelper.GetIconByFileExtension(filesUploaded.Location)"></i></span><span class="text-link">@filesUploaded.Name</span>
</a>
<div class="d-flex align-items-center">
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="editFileName('@filesUploaded.Location', this)"><i class="bi bi-pencil"></i></button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteFileFromUploadedFiles('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
</div>
</div>
</li>
}
</ul>
}
<script>
var uploadedFiles = [];
@foreach (UploadedFiles filesUploaded in Model)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
</script>

View File

@@ -0,0 +1,13 @@
@model IEnumerable<UserColumnPreference>
<script>
var visibleColumns = [];
var columnsOrder = [];
@foreach(string visibleColumn in Model.SelectMany(x=> x.VisibleColumns))
{
@:visibleColumns.push(decodeHTMLEntities('@visibleColumn'));
}
@foreach(string columnOrder in Model.SelectMany(x=>x.ColumnOrder)){
@:columnsOrder.push(decodeHTMLEntities('@columnOrder'));
}
loadUserColumnPreferences(visibleColumns, columnsOrder);
</script>

View File

@@ -0,0 +1,288 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model VehicleHistoryViewModel
@{
var userConfig = config.GetUserConfig(User);
var hideZero = userConfig.HideZero;
var userLanguage = userConfig.UserLanguage;
var extraFields = Model.ReportParameters.ExtraFields;
}
<div style="page-break-after: always;">
<div class="row mt-2">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
<div class="ms-5">
<span class="display-6">
@translator.Translate(userLanguage, "Vehicle Maintenance Report")
</span>
@if (!string.IsNullOrWhiteSpace(Model.StartDate) && !string.IsNullOrWhiteSpace(Model.EndDate))
{
<br />
<span class="lead ms-2">
@($"{@translator.Translate(userLanguage, "From")} {Model.StartDate} {@translator.Translate(userLanguage, "To")} {Model.EndDate}")
</span>
}
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
@if (!string.IsNullOrWhiteSpace(Model.VehicleData.LicensePlate))
{
<li class="list-group-item">
<span class="lead">@Model.VehicleData.LicensePlate</span>
</li>
}
@foreach(ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
<li class="list-group-item">
<div class="row">
<div class="col-4">
@if (Model.VehicleData.IsElectric)
{
<span><i class="bi bi-ev-station me-2"></i>@translator.Translate(userLanguage, "Electric")</span>
}
else if (Model.VehicleData.IsDiesel)
{
<span><i class="bi bi-fuel-pump-diesel me-2"></i>@translator.Translate(userLanguage, "Diesel")</span>
}
else
{
<span><i class="bi bi-fuel-pump me-2"></i>@translator.Translate(userLanguage, "Gasoline")</span>
}
</div>
@if (!string.IsNullOrWhiteSpace(Model.DaysOwned))
{
<div class="col-4">
<span><i class="bi bi-calendar-range me-2"></i>@($"{Model.DaysOwned} {translator.Translate(userLanguage, "Days")}")</span>
</div>
}
@if (Model.DistanceTraveled != default)
{
<div class="col-4">
<span><i class="bi bi-speedometer me-2"></i>@($"{Model.DistanceTraveled} {Model.DistanceUnit}")</span>
</div>
}
</div>
</li>
</ul>
</div>
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Last Reported Odometer Reading")}: {Model.Odometer}") </li>
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Average Fuel Economy")}: {Model.MPG}") </li>
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Total Spent(excl. fuel)")}: {Model.TotalCost.ToString("C")} ({Model.TotalCostPerMile.ToString("C")}/{Model.DistanceUnit})") </li>
<li class="list-group-item">@($"{translator.Translate(userLanguage, "Total Spent on Fuel")}: {Model.TotalGasCost.ToString("C")} ({Model.TotalGasCostPerMile.ToString("C")}/{Model.DistanceUnit})") </li>
</ul>
</div>
</div>
<hr />
@if (Model.TotalDepreciation != default)
{
<div class="row">
<div class="col-3">
@(Model.TotalDepreciation > 0 ? translator.Translate(userLanguage, "Depreciation") : translator.Translate(userLanguage, "Appreciation"))
</div>
<div class="col-3">
<span><i class="bi @(Model.TotalDepreciation > 0 ? "bi-graph-down-arrow" : "bi-graph-up-arrow") me-2"></i>@Math.Abs(Model.TotalDepreciation).ToString("C")</span>
</div>
@if (Model.DepreciationPerDay != default)
{
<div class="col-3">
<span><i class="bi bi-calendar-event me-2"></i>@($"{Model.DepreciationPerDay.ToString("C")}/{translator.Translate(userLanguage, "day")}")</span>
</div>
}
@if (Model.DepreciationPerMile != default)
{
<div class="col-3">
<span><i class="bi bi-speedometer me-2"></i>@($"{Model.DepreciationPerMile.ToString("C")}/{Model.DistanceUnit}")</span>
</div>
}
</div>
<hr />
}
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead>
<tr class="d-flex">
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.DataType)) ? "" : "d-none")">@translator.Translate(userLanguage, "Type")</th>
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Date)) ? "" : "d-none")">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Odometer)) ? "" : "d-none")">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Description)) ? "" : "d-none")">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Cost)) ? "" : "d-none")">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-4 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Notes)) ? "" : "d-none")">@translator.Translate(userLanguage, "Notes")</th>
@foreach(string extraField in extraFields)
{
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1">@extraField</th>
}
</tr>
</thead>
<tbody>
@foreach (GenericReportModel reportData in Model.VehicleHistory)
{
<tr class="d-flex">
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.DataType)) ? "" : "d-none")">
@if (reportData.DataType == ImportMode.ServiceRecord)
{
<span><i class="bi bi-card-checklist me-2"></i>@translator.Translate(userLanguage, "Service")</span>
}
else if (reportData.DataType == ImportMode.RepairRecord)
{
<span><i class="bi bi-exclamation-octagon me-2"></i>@translator.Translate(userLanguage, "Repair")</span>
}
else if (reportData.DataType == ImportMode.UpgradeRecord)
{
<span><i class="bi bi-wrench-adjustable me-2"></i>@translator.Translate(userLanguage, "Upgrade")</span>
}
else if (reportData.DataType == ImportMode.TaxRecord)
{
<span><i class="bi bi-currency-dollar me-2"></i>@translator.Translate(userLanguage, "Tax")</span>
}
</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Date)) ? "" : "d-none")">@reportData.Date.ToShortDateString()</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Odometer)) ? "" : "d-none")">@(reportData.Odometer == default ? "---" : reportData.Odometer.ToString("N0"))</td>
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Description)) ? "" : "d-none")">@reportData.Description</td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Cost)) ? "" : "d-none")">@(StaticHelper.HideZeroCost(reportData.Cost, hideZero))</td>
<td class="col-4 flex-grow-1 flex-shrink-1 text-wrap text-break @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Notes)) ? "" : "d-none")">@StaticHelper.TruncateStrings(reportData.Notes, 100)</td>
@foreach(string extraField in extraFields)
{
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1">@(reportData.ExtraFields.Where(x => x.Name == extraField)?.FirstOrDefault()?.Value ?? "")</td>
}
</tr>
}
</tbody>
<tfoot>
<tr class="d-flex">
<td class="col-12 showOnPrint motovaultpro-report-banner">
@StaticHelper.ReportNote
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
@if (Model.ReportParameters.PrintIndividualRecords){
@foreach (GenericReportModel genericRecord in Model.VehicleHistory)
{
<div class="d-flex flex-column recordSticker">
<div class="d-flex">
<img src="@config.GetLogoUrl()" class="motovaultpro-logo" />
</div>
<hr />
<div class="row">
<div class="col-6">
<ul class="list-group">
<li class="list-group-item">
<span class="display-6">@($"{Model.VehicleData.Year} {Model.VehicleData.Make} {Model.VehicleData.Model}")</span>
</li>
<li class="list-group-item">
<span class="lead">@($"{StaticHelper.GetVehicleIdentifier(Model.VehicleData)}")</span>
</li>
@foreach (ExtraField extraField in Model.VehicleData.ExtraFields)
{
if (!string.IsNullOrWhiteSpace(extraField.Value))
{
<li class="list-group-item">
<span class="lead">@($"{extraField.Name}: {extraField.Value}")</span>
</li>
}
}
</ul>
</div>
<div class="col-6">
<ul class="list-group">
@if (!string.IsNullOrWhiteSpace(genericRecord.Description))
{
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Description")}: {genericRecord.Description}")
</li>
}
@switch (genericRecord.DataType)
{
case ImportMode.ServiceRecord:
case ImportMode.RepairRecord:
case ImportMode.UpgradeRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Odometer")}: {genericRecord.Odometer}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {genericRecord.Cost.ToString("C")}")
</li>
break;
case ImportMode.TaxRecord:
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Date")}: {genericRecord.Date.ToShortDateString()}")
</li>
<li class="list-group-item">
@($"{translator.Translate(userLanguage, "Cost")}: {genericRecord.Cost.ToString("C")}")
</li>
break;
}
@foreach (ExtraField extraField in genericRecord.ExtraFields)
{
<li class="list-group-item">
@($"{extraField.Name}: {extraField.Value}")
</li>
}
</ul>
</div>
</div>
<hr />
@if (genericRecord.RequisitionHistory.Any())
{
<div class="row">
<div class="col-12">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Quantity")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Cost")</th>
</tr>
</thead>
<tbody>
@foreach (SupplyUsageHistory usageHistory in genericRecord.RequisitionHistory)
{
<tr class="d-flex">
<td class="col-2 text-truncate">@usageHistory.PartNumber</td>
<td class="col-6 text-truncate">@usageHistory.Description</td>
<td class="col-2">@usageHistory.Quantity.ToString("F")</td>
<td class="col-2">@usageHistory.Cost.ToString("C2")</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<hr />
}
<div class="row flex-grow-1 flex-shrink-1">
<div class="col-12">
<div class="stickerNote ms-1 me-1 p-1">
@(genericRecord.Notes)
</div>
</div>
</div>
</div>
}
<script>setMarkDownStickerNotes()</script>
}

View File

@@ -0,0 +1,163 @@
@using MotoVaultPro.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model Vehicle
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var isNew = Model.Id == 0;
if (Model.ImageLocation == "/defaults/noimage.png")
{
Model.ImageLocation = "";
}
}
<div class="modal-header">
<h5 class="modal-title" id="addVehicleModalLabel">@(isNew ? translator.Translate(userLanguage, "Add New Vehicle") : translator.Translate(userLanguage, "Edit Vehicle"))</h5>
@if (isNew)
{
<button type="button" class="btn-close" onclick="hideAddVehicleModal()" aria-label="Close"></button>
}
else if (!isNew)
{
<button type="button" class="btn-close" onclick="hideEditVehicleModal()" aria-label="Close"></button>
}
</div>
<div class="modal-body" onkeydown="handleEnter(this)">
<form class="form-inline">
<div class="form-group">
<div class="row">
<div class="col-12 col-md-6">
<label for="inputYear">@translator.Translate(userLanguage, "Year")</label>
<input type="number" inputmode="numeric" id="inputYear" class="form-control" placeholder="@translator.Translate(userLanguage, "Year(must be after 1900)")" value="@(isNew ? "" : Model.Year)">
<label for="inputMake">@translator.Translate(userLanguage, "Make")</label>
<input type="text" id="inputMake" class="form-control" placeholder="@translator.Translate(userLanguage, "Make")" value="@Model.Make">
<label for="inputModel">@translator.Translate(userLanguage, "Model")</label>
<input type="text" id="inputModel" class="form-control" placeholder="@translator.Translate(userLanguage, "Model")" value="@Model.Model">
<label for="inputLicensePlate">@translator.Translate(userLanguage, "License Plate")</label>
<input type="text" id="inputLicensePlate" class="form-control" placeholder="@translator.Translate(userLanguage, "License Plate")" value="@Model.LicensePlate">
@await Html.PartialAsync("_ExtraField", Model.ExtraFields)
<label for="inputIdentifier" class="@(!Model.ExtraFields.Any() ? "d-none" : "")">@translator.Translate(userLanguage, "Identifier")</label>
<select class="form-select @(!Model.ExtraFields.Any() ? "d-none" : "")" id="inputIdentifier" )>
<!option value="LicensePlate" @(Model.VehicleIdentifier == "LicensePlate" ? "selected" : "")>@translator.Translate(userLanguage, "License Plate")</!option>
@foreach(ExtraField field in Model.ExtraFields)
{
<!option value="@field.Name" @(Model.VehicleIdentifier == field.Name ? "selected" : "")>@field.Name</!option>
}
</select>
</div>
<div class="col-12 col-md-6">
<label for="inputFuelType">@translator.Translate(userLanguage, "Fuel Type")</label>
<select class="form-select" id="inputFuelType")>
<!option value="Gasoline" @(!Model.IsDiesel && !Model.IsElectric ? "selected" : "")>@translator.Translate(userLanguage, "Gasoline")</!option>
<!option value="Diesel" @(Model.IsDiesel ? "selected" : "")>@translator.Translate(userLanguage, "Diesel")</!option>
<!option value="Electric" @(Model.IsElectric ? "selected" : "")>@translator.Translate(userLanguage, "Electric")</!option>
</select>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputUseHours" checked="@Model.UseHours">
<label class="form-check-label" for="inputUseHours">@translator.Translate(userLanguage, "Use Engine Hours")</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputOdometerOptional" checked="@Model.OdometerOptional">
<label class="form-check-label" for="inputOdometerOptional">@translator.Translate(userLanguage, "Odometer Optional")</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" onchange="toggleOdometerAdjustment()" id="inputHasOdometerAdjustment" checked="@Model.HasOdometerAdjustment">
<label class="form-check-label" for="inputHasOdometerAdjustment">@translator.Translate(userLanguage, "Odometer Adjustments")</label>
</div>
<div class="collapse @(Model.HasOdometerAdjustment ? "show" : "")" id="odometerAdjustments">
<div>
<label for="inputOdometerMultiplier">@translator.Translate(userLanguage, "Odometer Multiplier")</label>
<input type="text" id="inputOdometerMultiplier" class="form-control" placeholder="@translator.Translate(userLanguage, "Odometer Multiplier")" value="@Model.OdometerMultiplier">
<label for="inputOdometerDifference">@translator.Translate(userLanguage, "Odometer Difference")</label>
<input type="text" id="inputOdometerDifference" class="form-control" placeholder="@translator.Translate(userLanguage, "Odometer Difference")" value="@Model.OdometerDifference">
</div>
</div>
<div class="accordion accordion-flush" id="vehicleModalAccordion">
<div class="accordion-item">
<div class="accordion-header">
<button class="accordion-button skinny collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePurchaseInfo">
@translator.Translate(userLanguage, "Purchase/Sold Information(optional)")
</button>
</div>
<div id="collapsePurchaseInfo" class="accordion-collapse collapse" data-bs-parent="#vehicleModalAccordion">
<label for="inputPurchaseDate">@translator.Translate(userLanguage, "Purchased Date(optional)")</label>
<div class="input-group">
<input type="text" id="inputPurchaseDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Date")" value="@Model.PurchaseDate">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="inputSoldDate">@translator.Translate(userLanguage, "Sold Date(optional)")</label>
<div class="input-group">
<input type="text" id="inputSoldDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Date")" value="@Model.SoldDate">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="inputPurchasePrice">@translator.Translate(userLanguage, "Purchased Price(optional)")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="inputPurchasePrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Price")" value="@(Model.PurchasePrice == default ? "" : Model.PurchasePrice)">
<label for="inputSoldPrice">@translator.Translate(userLanguage, "Sold Price(optional)")</label>
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="inputSoldPrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Price")" value="@(Model.SoldPrice == default ? "" : Model.SoldPrice)">
</div>
</div>
</div>
<div class="accordion accordion-flush" id="vehicleModalMetricAccordion">
<div class="accordion-item">
<div class="accordion-header">
<button class="accordion-button skinny collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseMetricInfo">
@translator.Translate(userLanguage, "Dashboard Metrics")
</button>
</div>
<div id="collapseMetricInfo" class="accordion-collapse collapse" data-bs-parent="#vehicleModalMetricAccordion">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputMetricDefault" value="@DashboardMetric.Default" checked="@Model.DashboardMetrics.Contains(DashboardMetric.Default)">
<label class="form-check-label" for="inputMetricDefault">@translator.Translate(userLanguage, "Last Odometer")</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputMetricCostPerMile" value="@DashboardMetric.CostPerMile" checked="@Model.DashboardMetrics.Contains(DashboardMetric.CostPerMile)">
<label class="form-check-label" for="inputMetricCostPerMile">@translator.Translate(userLanguage, "Total Cost / Total Distance Driven")</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="inputMetricTotalCost" value="@DashboardMetric.TotalCost" checked="@Model.DashboardMetrics.Contains(DashboardMetric.TotalCost)">
<label class="form-check-label" for="inputMetricTotalCost">@translator.Translate(userLanguage, "Total Cost")</label>
</div>
</div>
</div>
</div>
<label for="inputTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="inputTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
@if (!string.IsNullOrWhiteSpace(Model.ImageLocation))
{
<label for="inputImage">@translator.Translate(userLanguage, "Replace picture(optional)")</label>
<input onChange="uploadThumbnail(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
}
else
{
<label for="inputImage">@translator.Translate(userLanguage, "Upload a picture(optional)")</label>
<input onChange="uploadThumbnail(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
}
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (isNew)
{
<button type="button" class="btn btn-secondary" onclick="hideAddVehicleModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="saveVehicle(false)" class="btn btn-primary">@translator.Translate(userLanguage, "Add New Vehicle")</button>
} else if (!isNew)
{
<button type="button" class="btn btn-danger me-auto" onclick="deleteVehicle(@Model.Id)">@translator.Translate(userLanguage, "Delete Vehicle")</button>
<button type="button" class="btn btn-secondary" onclick="hideEditVehicleModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="saveVehicle(true)" class="btn btn-primary">@translator.Translate(userLanguage, "Save Vehicle")</button>
}
</div>
<script>
var uploadedFile = "@Model.ImageLocation";
function getVehicleModelData() {
return { id: @Model.Id}
}
</script>

View File

@@ -0,0 +1,4 @@
@using MotoVaultPro
@using MotoVaultPro.Models
@using Microsoft.Extensions.Options
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

3
Views/_ViewStart.cshtml Normal file
View File

@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}