Compare commits

...

158 Commits

Author SHA1 Message Date
Hargata Softworks
72a5960d40 Merge pull request #873 from hargata/Hargata/update.configurator
Fix Favicon Path
2025-02-27 12:04:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3429f1e4f9 Fix Favicon Path 2025-02-27 12:03:22 -07:00
Hargata Softworks
f8bea8bf81 Merge pull request #872 from hargata/Hargata/update.configurator
Update Configurator
2025-02-27 11:58:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
43794dd223 add feature to automatically merge new configurations into existing appsettings.json 2025-02-27 11:57:18 -07:00
Hargata Softworks
5cc84a7b46 Merge pull request #866 from hargata/Hargata/enter.key.qol
use generic enter key callback to handle key presses.
2025-02-19 16:09:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48248a4386 use generic enter key callback to handle key presses. 2025-02-19 16:08:15 -07:00
Hargata Softworks
95305402e6 Merge pull request #864 from hargata/Hargata/856
add markdown to kiosk notes.
2025-02-19 09:12:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
29f24c527f add markdown to kiosk notes. 2025-02-19 09:10:03 -07:00
Hargata Softworks
efa2bbf6cc Merge pull request #858 from hargata/Hargata/857
Add flag to print individual records
2025-02-12 10:24:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
66ed9ba699 add flag to print individual records when generating vehicle history report. 2025-02-12 10:23:19 -07:00
Hargata Softworks
e841e64f78 Merge pull request #852 from hargata/Hargata/dynamic.csv
dynamically generate locale-sensitive CSV samples
2025-02-10 11:15:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b0f46803c5 dynamically generate locale-sensitive CSV samples 2025-02-10 11:13:53 -07:00
Hargata Softworks
ac0bef5de0 Merge pull request #850 from hargata/Hargata/840.2
more hardening.
2025-02-07 12:37:22 -07:00
DESKTOP-T0O5CDB\DESK-555BD
08ea40a08e more hardening. 2025-02-07 12:36:43 -07:00
Hargata Softworks
1d8e8059cd Merge pull request #849 from hargata/Hargata/848
fixed MPG label bugs when all tagged records are partial fuel ups.
2025-02-07 08:33:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
20b4396a4c simplified code. 2025-02-07 08:32:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4c39cb4d06 fixed MPG label bugs when all tagged records are partial fuel ups. 2025-02-07 08:29:18 -07:00
Hargata Softworks
f58e6abd9f Update issue templates 2025-02-06 22:48:32 -07:00
Hargata Softworks
4f1e83a7d7 Update issue templates 2025-02-06 22:46:55 -07:00
Hargata Softworks
e39b38c2d5 Update CONTRIBUTING.md 2025-02-06 22:35:38 -07:00
Hargata Softworks
b9ffa6ce91 Merge pull request #845 from hargata/Hargata/780
further hardening.
2025-02-05 16:28:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
76c6753785 further hardening. 2025-02-05 16:27:45 -07:00
Hargata Softworks
54f5062377 Merge pull request #844 from hargata/Hargata/780
add functionality to re-arrange table columns.
2025-02-05 16:01:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7e60fd9e40 reset var. 2025-02-05 16:00:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
926947bae4 add functionality to re-arrange table columns. 2025-02-05 15:56:20 -07:00
Hargata Softworks
2652e47018 Merge pull request #841 from hargata/Hargata/840
Hargata/840
2025-02-05 07:06:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c1500b6ed0 bump version 2025-02-04 13:22:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
125bc44d2e add put and delete api endpoints. 2025-02-04 13:15:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c317c0a058 Add POST API Endpoint to add plan records. 2025-02-04 10:07:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
beb9498399 standardize this stuff. 2025-02-03 16:50:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4e74940684 added whoami endpoint 2025-02-03 16:02:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c0f73080d2 Add APIAuth Role 2025-02-03 13:34:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
cd0a35537b API endpoint for Planner 2025-02-03 13:20:08 -07:00
Hargata Softworks
4d5020b607 Merge pull request #839 from hargata/Hargata/805
re-send registration token email.
2025-02-03 08:51:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7d8b7596ce fixed wording. 2025-02-03 08:51:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
08b5c9f25a re-send registration token email. 2025-02-03 08:35:33 -07:00
Hargata Softworks
82a4f8d57b Merge pull request #833 from hargata/Hargata/831
translatable Email correspondence
2025-01-26 10:28:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
538a79319e translatable Email correspondence 2025-01-26 10:26:41 -07:00
Hargata Softworks
0555dcbc43 Merge pull request #830 from hargata/Hargata/824
take into account purchase and sold date.
2025-01-24 14:02:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7b1f19ff9f take into account purchase and sold date. 2025-01-24 14:00:26 -07:00
Hargata Softworks
9cac427eb9 Merge pull request #829 from hargata/Hargata/823
add attachments column
2025-01-24 13:13:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1e5950028f add attachments column 2025-01-24 13:01:56 -07:00
Hargata Softworks
7a7d343c3f Merge pull request #828 from hargata/Hargata/824
additional logic taking into account vehicle sold date.
2025-01-24 10:40:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ebf6388414 additional logic taking into account vehicle sold date. 2025-01-24 10:39:42 -07:00
Hargata Softworks
fa5426be53 Merge pull request #827 from hargata/Hargata/824
Fix number of days used when calculating cost per day.
2025-01-24 10:22:23 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4c60bb20c9 Fix number of days used when calculating cost per day. 2025-01-24 10:21:40 -07:00
Hargata Softworks
b02f3c8f8b Merge pull request #826 from hargata/Hargata/825
825
2025-01-24 09:17:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d5f769e5a4 Fixed the cost per distance sum table to use total cost per year divided by total distance per year. 2025-01-24 09:16:42 -07:00
Hargata Softworks
2afd1188eb Merge pull request #820 from hargata/Hargata/804
only make certain api endpoints testable
2025-01-22 12:09:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c6301242d make urgencies endpoint parameters optional. 2025-01-22 12:07:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
06016727d9 only make certain api endpoints testable 2025-01-22 11:52:16 -07:00
Hargata Softworks
1ee0c27ff9 Merge pull request #819 from hargata/Hargata/804
make it easier to test API.
2025-01-22 11:34:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7f03a630c6 make it easier to test API. 2025-01-22 11:33:55 -07:00
Hargata Softworks
2aa19f4f3b Merge pull request #818 from hargata/Hargata/tag.bug.fix
Fix count labels for odometer and reminder records when filtered by tags
2025-01-22 09:40:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4be3c16adc Fix count labels for odometer and reminder records when filtered by tags. 2025-01-22 09:39:07 -07:00
Hargata Softworks
f1f99a67dd Merge pull request #816 from hargata/Hargata/refine.sort
only add the default sorter once.
2025-01-21 15:59:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a4f15ffe15 only add the default sorter once. 2025-01-21 15:57:50 -07:00
Hargata Softworks
c11dad0cb3 Merge pull request #815 from hargata/Hargata/refine.sort
get rid of storedRowTableState and all the bugs that comes with it.
2025-01-21 14:11:28 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2bf2469657 get rid of storedRowTableState and all the bugs that comes with it. 2025-01-21 14:08:44 -07:00
Hargata Softworks
a23b02e962 Merge pull request #814 from hargata/Hargata/805.2
streamline token generation process.
2025-01-20 13:14:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
905ee9bf27 streamline token generation process. 2025-01-20 13:14:04 -07:00
Hargata Softworks
e26869b30b Merge pull request #813 from hargata/Hargata/755
add days as an interval for recurring tax records and reminders
2025-01-20 12:55:56 -07:00
DESKTOP-T0O5CDB\DESK-555BD
90867792d9 missing semicolon 2025-01-20 12:51:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1238802a75 More Month to Time update. 2025-01-20 12:51:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8f7c50b88f Change label. 2025-01-20 12:48:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
df24434689 add days as an interval for recurring tax records and reminders 2025-01-20 12:42:59 -07:00
Hargata Softworks
878748803d Merge pull request #812 from hargata/Hargata/tech.debt
Fixed unreachable code and used Append instead of Add.
2025-01-19 12:34:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
80bbf7f22f Fixed unreachable code and used Append instead of Add. 2025-01-19 12:33:50 -07:00
Hargata Softworks
28b0ea565a Merge pull request #811 from hargata/Hargata/801.3
Add sticker printing for supplies.
2025-01-19 10:37:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
07ccd1dea9 Add sticker printing for supplies. 2025-01-19 10:36:32 -07:00
Hargata Softworks
6a8fba535a Merge pull request #810 from hargata/Hargata/801.2
added extra field and supplies to the sticker print.
2025-01-17 11:37:10 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f1b82ad0d3 removed unused row class. 2025-01-17 11:36:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0035bc09dd added extra field and supplies to the sticker print. 2025-01-17 11:35:41 -07:00
Hargata Softworks
863c57d7fb Merge pull request #808 from hargata/Hargata/801.2
add sticker printing for odometer and plan records.
2025-01-17 09:11:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
00ccb68ce2 add sticker printing for odometer and plan records. 2025-01-17 09:09:19 -07:00
Hargata Softworks
c2995dcd25 Merge pull request #806 from hargata/Hargata/805
enable open registration.
2025-01-16 11:23:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7b63366627 enable open registration. 2025-01-16 11:20:44 -07:00
Hargata Softworks
ababa6bf27 Merge pull request #803 from hargata/Hargata/801
Hargata/801 - Printable Stickers
2025-01-16 10:24:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3f1f42d4f0 add vehicle extra fields and fixed styling 2025-01-16 10:24:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d76bd6d36 added printing functionality for notes, fuel, and tax. 2025-01-15 11:54:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ab34d1682c Added ability to print individual service, repair, and upgrade records. 2025-01-15 09:49:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
552b7a6356 simplified design. 2025-01-12 15:26:08 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f86acf673a you can now print reminders. 2025-01-12 14:47:18 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c6f49bafca Added sticker viewModel and some code cleanup. 2025-01-12 12:28:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fdbb325611 simplify code. 2025-01-12 12:04:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8c557ced85 draft PR for sticker printing. 2025-01-11 12:04:01 -07:00
Hargata Softworks
ad0f7de506 Merge pull request #802 from hargata/Hargata/697
Document the Calendar API along with support for auth.
2025-01-10 19:46:11 -07:00
DESKTOP-T0O5CDB\DESK-555BD
15ec9bb454 Document the Calendar API along with support for auth. 2025-01-10 19:43:09 -07:00
Hargata Softworks
9fe7558cfe Merge pull request #792 from hargata/Hargata/260
Simplify volume mounts.
2025-01-10 13:53:31 -07:00
Hargata Softworks
79633dbe1d Merge pull request #799 from hargata/Hargata/796
Update configurator
2025-01-10 12:13:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f325306e20 Update configurator 2025-01-10 12:13:34 -07:00
Hargata Softworks
4778656063 Merge pull request #798 from hargata/Hargata/796
fix mileage not included.
2025-01-10 11:02:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f7e00523c2 fix mileage not included. 2025-01-10 11:01:24 -07:00
Hargata Softworks
bf3df7230b Merge pull request #797 from hargata/Hargata/796
Hargata/796
2025-01-10 10:03:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c5182e0ed6 lol 2025-01-10 10:03:09 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4081438cba Defer calculations if odometer not provided. 2025-01-10 10:01:16 -07:00
Hargata Softworks
b72fe2bf37 Merge pull request #793 from hargata/Hargata/warn.dir.diff
Warn when working directory differs from content path.
2025-01-09 10:02:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
4d9c687709 Warn when working directory differs from content path. 2025-01-09 10:02:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3d4b970967 Simplify volume mounts. 2025-01-08 20:56:37 -07:00
Hargata Softworks
792f295c45 Merge pull request #791 from hargata/Hargata/769
Fixed backup restoration for userConfig.json
2025-01-08 20:38:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
af28753558 use a static string instead. 2025-01-08 20:37:45 -07:00
DESKTOP-T0O5CDB\DESK-555BD
24fb663599 Fixed backup restoration for userConfig.json 2025-01-08 20:33:38 -07:00
Hargata Softworks
140506c9c3 Merge pull request #790 from hargata/Hargata/769
moved userConfig file into data folder.
2025-01-08 20:26:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
519f159c8c moved userConfig file into data folder. 2025-01-08 20:25:17 -07:00
Hargata Softworks
bb019cbcd9 Merge pull request #789 from hargata/Hargata/769
Fix gas record files attachment
2025-01-08 16:19:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
84219627ff Fix gas record files attachment 2025-01-08 16:18:36 -07:00
Hargata Softworks
f218e878c6 Merge pull request #788 from hargata/Hargata/769
Document Upload Endpoint
2025-01-08 15:55:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
520f47955b cleaned up usings. 2025-01-08 14:15:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8110ee18f1 Create endpoint to upload documents and add ability to attach uploaded documents to records. 2025-01-08 14:14:23 -07:00
Hargata Softworks
55bf817310 Merge pull request #782 from hargata/Hargata/697
added API method that generates a calendar of reminders for the user.
2025-01-08 12:39:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2f55f8118 Restore favicon, previously was served up automatically by browser but now we want to link it explicitly. also brought back the security features for accessing static files. 2025-01-08 12:38:27 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f48e7cd0d4 update Controller Action Name, 2025-01-08 11:11:29 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c82e0c8b9b Use a MD5 hash to get exactly 16 bytes so the GUID is always valid and identical for the calendar event with same date and description. 2025-01-08 10:49:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c72877e16b Merge branch 'main' into Hargata/697 2025-01-08 10:40:10 -07:00
Hargata Softworks
ccc9076397 Merge pull request #786 from hargata/Hargata/785
move files out of webrootpath.
2025-01-08 10:13:03 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1c716ecf4a bump version 2025-01-08 10:12:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
2cc471b944 move files out of webrootpath. 2025-01-08 10:10:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a6c450109a added API method that generates a calendar of reminders for the user. 2025-01-07 08:43:25 -07:00
Hargata Softworks
e13707305b Merge pull request #776 from hargata/Hargata/745
Fix annoying bug on macOS devices where context menu hides as soon as…
2024-12-31 09:21:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
68bc0383f4 Fix annoying bug on macOS devices where context menu hides as soon as it shows up. 2024-12-31 09:21:05 -07:00
Hargata Softworks
878f4f3687 Merge pull request #771 from hargata/Hargata/745
added bottom padding for garage view.
2024-12-16 17:32:20 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48a721adda added bottom padding for garage view. 2024-12-16 17:31:30 -07:00
Hargata Softworks
548e8da78c Merge pull request #747 from hargata/Hargata/745
1.4.2 Changes
2024-12-16 09:45:01 -07:00
Hargata Softworks
beb28214cf Merge pull request #767 from hargata/Hargata/758
allow users to create odometer records from existing records.
2024-12-13 11:37:47 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b4bb31491b allow users to create odometer records from existing records. 2024-12-13 11:31:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0b5f007327 Styling fixes. 2024-12-13 10:59:53 -07:00
Hargata Softworks
d31a4aeea3 Merge pull request #766 from hargata/Hargata/761
Add Date Range Filter to Vehicle Maintenance Report
2024-12-12 11:58:44 -07:00
DESKTOP-T0O5CDB\DESK-555BD
97acd873eb Added date range filter. 2024-12-12 11:56:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5cf3538be3 garage view enhancement for mobile devices. 2024-12-11 19:19:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a34921701d webhook payload update. 2024-12-11 14:40:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5fcf54de09 cleaned up some code. 2024-12-11 12:32:40 -07:00
Hargata Softworks
68372bf995 Merge pull request #764 from hargata/Hargata/763
Hargata/763
2024-12-11 12:29:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9f99ac1531 Add tags to be exported. 2024-12-11 12:28:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7384fda6b Spacing and translation keys. 2024-12-07 22:34:02 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8f4d610825 add spacing in report parameter. 2024-12-07 22:25:28 -07:00
Hargata Softworks
6bbf4b2575 Merge pull request #760 from hargata/Hargata/572
Hargata/572
2024-12-07 12:03:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
32db884620 Allow users to filter records by tags in the consolidated report. 2024-12-07 11:50:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1b736b36f8 Added delete records. 2024-12-06 11:34:04 -07:00
Hargata Softworks
22dfe5eb04 Merge pull request #756 from hargata/Hargata/574
Created nicer machine payloads for webhooks.
2024-12-06 08:43:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8fd49e20d5 added support for discord. 2024-12-06 08:42:25 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c791070126 Fixed webhook stuff. 2024-12-06 08:11:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
807603fd4f Created nicer machine payloads for webhooks. 2024-12-05 19:08:13 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a944e5e5ab See #744 2024-12-05 08:10:49 -07:00
DESKTOP-T0O5CDB\DESK-555BD
33e916cac2 force a page refresh if there are oudated recurring tax records on vehicle load. 2024-12-04 21:11:58 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a99d71dc41 pattern 2024-12-04 11:54:43 -07:00
DESKTOP-T0O5CDB\DESK-555BD
67d91f1a0d See 751 2024-12-04 11:38:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
de2db7d24e added security checks 2024-12-04 09:05:49 -07:00
Hargata Softworks
b360f77ea0 Merge pull request #754 from hargata/Hargata/752.2
Fixed naming policy.
2024-12-04 08:54:43 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e869528522 Fixed naming policy. 2024-12-04 08:15:46 -07:00
Hargata Softworks
7c0317d232 Merge pull request #753 from hargata/Hargata/752
Allow for JSON Body in POST/PUT API Methods
2024-12-03 14:15:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0e49497da1 added invariant exports to get methods. 2024-12-03 13:40:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0380382c47 allow API methods to receive JSON with rich datatypes. 2024-12-03 11:35:11 -07:00
Hargata Softworks
23201b8c86 Merge pull request #748 from hargata/Hargata/541
Added PUT API Methods
2024-11-30 12:25:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5f357c9f84 Fix CollaboratorFilter bug. 2024-11-30 12:23:48 -07:00
DESKTOP-T0O5CDB\DESK-555BD
244272621c bump version 2024-11-30 10:19:51 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5792b84880 Added PUT API Methods 2024-11-30 10:17:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
8e644093f9 added try catch 2024-11-29 13:47:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9421cb57ca Create past records for outdated tax records. 2024-11-29 13:37:37 -07:00
109 changed files with 4259 additions and 645 deletions

View File

@@ -8,6 +8,8 @@ Ideally, the Issues tab should only consist of bug reports and feature requests
## Feature Requests ## Feature Requests
Feature Requests are cool, but we do want to avoid bloat and scope/feature creep. Feature Requests are cool, but we do want to avoid bloat and scope/feature creep.
Read [this](https://github.com/hargata/lubelog/wiki/Scope-and-Purpose) to better understand the scope and purpose of this project.
LubeLogger is a Vehicle Maintenance and Fuel Mileage Tracker. LubeLogger is a Vehicle Maintenance and Fuel Mileage Tracker.
It is not and should not be used for the following: It is not and should not be used for the following:
- Project Management Software(e.g.: Jira) - Project Management Software(e.g.: Jira)

View File

@@ -1,7 +1,7 @@
--- ---
name: Bug report name: Bug report
about: Report a bug about: Report a bug
title: '' title: "[BUG]"
labels: '' labels: ''
assignees: '' assignees: ''
@@ -18,7 +18,7 @@ Please make sure you have performed the following steps before opening a new bug
**Platform** **Platform**
- [ ] Docker Image - [ ] Docker Image
- [ ] Windows Standalone Executable - [ ] Windows/Linux Standalone Executable
**Browser Console Errors(F12)** **Browser Console Errors(F12)**
<!-- Attach a screenshot or codeblock containing the browser console error --> <!-- Attach a screenshot or codeblock containing the browser console error -->

View File

@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE REQUEST]"
labels: ''
assignees: ''
---
**Checklist**
Please make sure you have performed the following steps before submitting a new feature request, change `[ ]` to `[x]` to mark it as done
- [ ] I have read the [Contributing Guidelines](https://github.com/hargata/lubelog/blob/main/.github/CONTRIBUTING.md) and [Scope and Purpose](https://github.com/hargata/lubelog/wiki/Scope-and-Purpose)
- [ ] I have searched through existing issues.
**Description**
<!-- Describe the feature request below this line -->

11
.gitignore vendored
View File

@@ -1,15 +1,6 @@
.vs/ .vs/
bin/ bin/
obj/ obj/
wwwroot/images/ data/
cartracker.db
data/cartracker.db
wwwroot/documents/
wwwroot/temp/
wwwroot/imports/
wwwroot/translations/
config/userConfig.json
CarCareTracker.csproj.user CarCareTracker.csproj.user
Properties/launchSettings.json Properties/launchSettings.json
data/cartracker-log.db
data/widgets.html

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ namespace CarCareTracker.Controllers
{ {
public IActionResult Unauthorized() public IActionResult Unauthorized()
{ {
if (!User.IsInRole("CookieAuth")) if (User.IsInRole("APIAuth"))
{ {
Response.StatusCode = 403; Response.StatusCode = 403;
return new EmptyResult(); return new EmptyResult();

View File

@@ -81,7 +81,7 @@ namespace CarCareTracker.Controllers
private string UploadFile(IFormFile fileToUpload) private string UploadFile(IFormFile fileToUpload)
{ {
string uploadDirectory = "temp/"; string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory); string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath)) if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath); Directory.CreateDirectory(uploadPath);
string fileName = Guid.NewGuid() + Path.GetExtension(fileToUpload.FileName); string fileName = Guid.NewGuid() + Path.GetExtension(fileToUpload.FileName);
@@ -95,7 +95,7 @@ namespace CarCareTracker.Controllers
public IActionResult UploadCoordinates(List<string> coordinates) public IActionResult UploadCoordinates(List<string> coordinates)
{ {
string uploadDirectory = "temp/"; string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory); string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath)) if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath); Directory.CreateDirectory(uploadPath);
string fileName = Guid.NewGuid() + ".csv"; string fileName = Guid.NewGuid() + ".csv";

View File

@@ -98,7 +98,6 @@ namespace CarCareTracker.Controllers
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true); var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
return PartialView("_KioskPlan", kioskResult); return PartialView("_KioskPlan", kioskResult);
} }
break;
case KioskMode.Reminder: case KioskMode.Reminder:
{ {
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false); var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);

View File

@@ -240,6 +240,12 @@ namespace CarCareTracker.Controllers
return Json(result); return Json(result);
} }
[HttpPost] [HttpPost]
public IActionResult SendRegistrationToken(LoginModel credentials)
{
var result = _loginLogic.SendRegistrationToken(credentials);
return Json(result);
}
[HttpPost]
public IActionResult RequestResetPassword(LoginModel credentials) public IActionResult RequestResetPassword(LoginModel credentials)
{ {
var result = _loginLogic.RequestResetPassword(credentials); var result = _loginLogic.RequestResetPassword(credentials);

View File

@@ -35,6 +35,11 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord) public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), gasRecord.VehicleId))
{
return Json(false);
}
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert) if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{ {
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
@@ -49,10 +54,11 @@ namespace CarCareTracker.Controllers
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord()); var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), gasRecord.VehicleId, User.Identity.Name, $"{(gasRecord.Id == default ? "Created" : "Edited")} Gas Record - Mileage: {gasRecord.Mileage.ToString()}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(gasRecord.ToGasRecord(), gasRecord.Id == default ? "gasrecord.add" : "gasrecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet] [HttpGet]
public IActionResult GetAddGasRecordPartialView(int vehicleId) public IActionResult GetAddGasRecordPartialView(int vehicleId)
{ {
@@ -65,6 +71,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetGasRecordForEditById(int gasRecordId) public IActionResult GetGasRecordForEditById(int gasRecordId)
{ {
var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId); var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
var convertedResult = new GasRecordInput var convertedResult = new GasRecordInput
{ {
Id = result.Id, Id = result.Id,
@@ -100,16 +111,16 @@ namespace CarCareTracker.Controllers
return false; return false;
} }
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id); var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(existingRecord, "gasrecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteGasRecordById(int gasRecordId) public IActionResult DeleteGasRecordById(int gasRecordId)
{ {
var result = DeleteGasRecordWithChecks(gasRecordId); var result = DeleteGasRecordWithChecks(gasRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");
}
return Json(result); return Json(result);
} }
[HttpPost] [HttpPost]

View File

@@ -16,6 +16,176 @@ namespace CarCareTracker.Controllers
{ {
return PartialView("_BulkDataImporter", mode); return PartialView("_BulkDataImporter", mode);
} }
[HttpGet]
public IActionResult GenerateCsvSample(ImportMode mode)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
switch (mode)
{
case ImportMode.ServiceRecord:
case ImportMode.RepairRecord:
case ImportMode.UpgradeRecord:
{
var exportData = new List<GenericRecordExportModel> { new GenericRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
Description = "Test",
Cost = 123.45M.ToString("C"),
Notes = "Test Note",
Odometer = 12345.ToString(),
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.GasRecord:
{
var exportData = new List<GasRecordExportModel> { new GasRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
Odometer = 12345.ToString(),
FuelConsumed = 12.34M.ToString(),
Cost = 45.67M.ToString("C"),
IsFillToFull = true.ToString(),
MissedFuelUp = false.ToString(),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteGasRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.OdometerRecord:
{
var exportData = new List<OdometerRecordExportModel> { new OdometerRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
InitialOdometer = 12345.ToString(),
Odometer = 12345.ToString(),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteOdometerRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.TaxRecord:
{
var exportData = new List<TaxRecordExportModel> { new TaxRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
Description = "Test",
Cost = 123.45M.ToString("C"),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteTaxRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.SupplyRecord:
{
var exportData = new List<SupplyRecordExportModel> { new SupplyRecordExportModel
{
Date = DateTime.Now.ToShortDateString(),
PartNumber = "TEST-123456",
PartSupplier = "Test Supplier",
PartQuantity = 1.5M.ToString(),
Description = "Test",
Cost = 123.45M.ToString("C"),
Notes = "Test Note",
Tags = "test1 test2"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteSupplyRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
case ImportMode.PlanRecord:
{
var exportData = new List<PlanRecordExportModel> { new PlanRecordExportModel
{
DateCreated = DateTime.Now.ToString(),
DateModified = DateTime.Now.ToString(),
Description = "Test",
Type = ImportMode.RepairRecord.ToString(),
Priority = PlanPriority.Normal.ToString(),
Progress = PlanProgress.Testing.ToString(),
Cost = 123.45M.ToString("C"),
Notes = "Test Note"
} };
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WritePlanRecordExportModel(csv, exportData);
}
writer.Dispose();
}
}
break;
default:
return Json(OperationResponse.Failed("No parameters"));
}
try
{
var fileBytes = _fileHelper.GetFileBytes(fullExportFilePath, true);
if (fileBytes.Length > 0)
{
return File(fileBytes, "text/csv", $"{mode.ToString().ToLower()}sample.csv");
}
else
{
return Json(OperationResponse.Failed("An error has occurred while generating CSV sample: file has zero bytes"));
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(OperationResponse.Failed($"An error has occurred while generating CSV sample: {ex.Message}"));
}
}
[TypeFilter(typeof(CollaboratorFilter))] [TypeFilter(typeof(CollaboratorFilter))]
[HttpGet] [HttpGet]
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode) public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
@@ -25,7 +195,7 @@ namespace CarCareTracker.Controllers
return Json(false); return Json(false);
} }
string uploadDirectory = "temp/"; string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory); string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", uploadDirectory);
if (!Directory.Exists(uploadPath)) if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath); Directory.CreateDirectory(uploadPath);
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv"; var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";

View File

@@ -26,11 +26,17 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveNoteToVehicleId(Note note) public IActionResult SaveNoteToVehicleId(Note note)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), note.VehicleId))
{
return Json(false);
}
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList(); note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
bool isCreate = note.Id == default; //needed here since Notes don't use an input object.
var result = _noteDataAccess.SaveNoteToVehicle(note); var result = _noteDataAccess.SaveNoteToVehicle(note);
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), note.VehicleId, User.Identity.Name, $"{(note.Id == default ? "Created" : "Edited")} Note - Description: {note.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(note, isCreate ? "noterecord.add" : "noterecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
@@ -43,6 +49,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetNoteForEditById(int noteId) public IActionResult GetNoteForEditById(int noteId)
{ {
var result = _noteDataAccess.GetNoteById(noteId); var result = _noteDataAccess.GetNoteById(noteId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
return PartialView("_NoteModal", result); return PartialView("_NoteModal", result);
} }
private bool DeleteNoteWithChecks(int noteId) private bool DeleteNoteWithChecks(int noteId)
@@ -54,16 +65,16 @@ namespace CarCareTracker.Controllers
return false; return false;
} }
var result = _noteDataAccess.DeleteNoteById(existingRecord.Id); var result = _noteDataAccess.DeleteNoteById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromNoteRecord(existingRecord, "noterecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteNoteById(int noteId) public IActionResult DeleteNoteById(int noteId)
{ {
var result = DeleteNoteWithChecks(noteId); var result = DeleteNoteWithChecks(noteId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");
}
return Json(result); return Json(result);
} }
[HttpPost] [HttpPost]

View File

@@ -39,15 +39,21 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord) public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), odometerRecord.VehicleId))
{
return Json(false);
}
//move files from temp. //move files from temp.
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList(); odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord()); var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), odometerRecord.VehicleId, User.Identity.Name, $"{(odometerRecord.Id == default ? "Created" : "Edited")} Odometer Record - Mileage: {odometerRecord.Mileage.ToString()}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord.ToOdometerRecord(), odometerRecord.Id == default ? "odometerrecord.add" : "odometerrecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet] [HttpGet]
public IActionResult GetAddOdometerRecordPartialView(int vehicleId) public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
{ {
@@ -125,6 +131,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetOdometerRecordForEditById(int odometerRecordId) public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
{ {
var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId); var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object. //convert to Input object.
var convertedResult = new OdometerRecordInput var convertedResult = new OdometerRecordInput
{ {
@@ -149,16 +160,16 @@ namespace CarCareTracker.Controllers
return false; return false;
} }
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id); var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(existingRecord, "odometerrecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteOdometerRecordById(int odometerRecordId) public IActionResult DeleteOdometerRecordById(int odometerRecordId)
{ {
var result = DeleteOdometerRecordWithChecks(odometerRecordId); var result = DeleteOdometerRecordWithChecks(odometerRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
}
return Json(result); return Json(result);
} }
} }

View File

@@ -17,6 +17,11 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord) public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId))
{
return Json(false);
}
//populate createdDate //populate createdDate
if (planRecord.Id == default) if (planRecord.Id == default)
{ {
@@ -35,18 +40,23 @@ namespace CarCareTracker.Controllers
} }
if (planRecord.DeletedRequisitionHistory.Any()) if (planRecord.DeletedRequisitionHistory.Any())
{ {
RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
} }
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord()); var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), planRecord.VehicleId, User.Identity.Name, $"{(planRecord.Id == default ? "Created" : "Edited")} Plan Record - Description: {planRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(planRecord.ToPlanRecord(), planRecord.Id == default ? "planrecord.add" : "planrecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
[HttpPost] [HttpPost]
public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord) public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), planRecord.VehicleId))
{
return Json(OperationResponse.Failed("Access Denied"));
}
//check if template name already taken. //check if template name already taken.
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any(); var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
if (planRecord.Id == default && existingRecord) if (planRecord.Id == default && existingRecord)
@@ -67,6 +77,16 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId) public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId)
{ {
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(false);
}
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return Json(false);
}
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId); var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
return Json(result); return Json(result);
} }
@@ -78,6 +98,11 @@ namespace CarCareTracker.Controllers
{ {
return Json(OperationResponse.Failed("Unable to find template")); return Json(OperationResponse.Failed("Unable to find template"));
} }
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return Json(OperationResponse.Failed("Access Denied"));
}
if (existingRecord.Supplies.Any()) if (existingRecord.Supplies.Any())
{ {
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies); var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
@@ -96,6 +121,11 @@ namespace CarCareTracker.Controllers
{ {
return Json(OperationResponse.Failed("Unable to find template")); return Json(OperationResponse.Failed("Unable to find template"));
} }
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return Json(OperationResponse.Failed("Access Denied"));
}
if (existingRecord.Supplies.Any()) if (existingRecord.Supplies.Any())
{ {
//check if all supplies are available //check if all supplies are available
@@ -156,6 +186,11 @@ namespace CarCareTracker.Controllers
return Json(false); return Json(false);
} }
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId); var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return Json(false);
}
existingRecord.Progress = planProgress; existingRecord.Progress = planProgress;
existingRecord.DateModified = DateTime.Now; existingRecord.DateModified = DateTime.Now;
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord); var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
@@ -239,6 +274,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetPlanRecordForEditById(int planRecordId) public IActionResult GetPlanRecordForEditById(int planRecordId)
{ {
var result = _planRecordDataAccess.GetPlanRecordById(planRecordId); var result = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object. //convert to Input object.
var convertedResult = new PlanRecordInput var convertedResult = new PlanRecordInput
{ {
@@ -271,12 +311,12 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies if it has not been converted to other record types. //restore any requisitioned supplies if it has not been converted to other record types.
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done) if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
{ {
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
} }
var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id); var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id);
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(existingRecord, "planrecord.delete", User.Identity.Name));
} }
return Json(result); return Json(result);
} }

View File

@@ -60,6 +60,7 @@ namespace CarCareTracker.Controllers
result = result.OrderByDescending(x => x.Urgency).ToList(); result = result.OrderByDescending(x => x.Urgency).ToList();
return PartialView("_ReminderRecords", result); return PartialView("_ReminderRecords", result);
} }
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet] [HttpGet]
public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId) public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId)
{ {
@@ -105,10 +106,15 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord) public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), reminderRecord.VehicleId))
{
return Json(false);
}
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord()); var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), reminderRecord.VehicleId, User.Identity.Name, $"{(reminderRecord.Id == default ? "Created" : "Edited")} Reminder - Description: {reminderRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord.ToReminderRecord(), reminderRecord.Id == default ? "reminderrecord.add" : "reminderrecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
@@ -128,6 +134,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetReminderRecordForEditById(int reminderRecordId) public IActionResult GetReminderRecordForEditById(int reminderRecordId)
{ {
var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId); var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object. //convert to Input object.
var convertedResult = new ReminderRecordInput var convertedResult = new ReminderRecordInput
{ {
@@ -145,6 +156,7 @@ namespace CarCareTracker.Controllers
ReminderMonthInterval = result.ReminderMonthInterval, ReminderMonthInterval = result.ReminderMonthInterval,
CustomMileageInterval = result.CustomMileageInterval, CustomMileageInterval = result.CustomMileageInterval,
CustomMonthInterval = result.CustomMonthInterval, CustomMonthInterval = result.CustomMonthInterval,
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
Tags = result.Tags Tags = result.Tags
}; };
return PartialView("_ReminderRecordModal", convertedResult); return PartialView("_ReminderRecordModal", convertedResult);
@@ -158,16 +170,16 @@ namespace CarCareTracker.Controllers
return false; return false;
} }
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id); var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(existingRecord, "reminderrecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteReminderRecordById(int reminderRecordId) public IActionResult DeleteReminderRecordById(int reminderRecordId)
{ {
var result = DeleteReminderRecordWithChecks(reminderRecordId); var result = DeleteReminderRecordWithChecks(reminderRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
}
return Json(result); return Json(result);
} }
} }

View File

@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord) public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), collisionRecord.VehicleId))
{
return Json(false);
}
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert) if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{ {
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
} }
if (collisionRecord.DeletedRequisitionHistory.Any()) if (collisionRecord.DeletedRequisitionHistory.Any())
{ {
RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
} }
//push back any reminders //push back any reminders
if (collisionRecord.ReminderRecordId.Any()) if (collisionRecord.ReminderRecordId.Any())
@@ -61,7 +66,7 @@ namespace CarCareTracker.Controllers
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord()); var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), collisionRecord.VehicleId, User.Identity.Name, $"{(collisionRecord.Id == default ? "Created" : "Edited")} Repair Record - Description: {collisionRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(collisionRecord.ToCollisionRecord(), collisionRecord.Id == default ? "repairrecord.add" : "repairrecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
@@ -74,6 +79,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetCollisionRecordForEditById(int collisionRecordId) public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
{ {
var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId); var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object. //convert to Input object.
var convertedResult = new CollisionRecordInput var convertedResult = new CollisionRecordInput
{ {
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies. //restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any()) if (existingRecord.RequisitionHistory.Any())
{ {
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
} }
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id); var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "repairrecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteCollisionRecordById(int collisionRecordId) public IActionResult DeleteCollisionRecordById(int collisionRecordId)
{ {
var result = DeleteCollisionRecordWithChecks(collisionRecordId); var result = DeleteCollisionRecordWithChecks(collisionRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
}
return Json(result); return Json(result);
} }
} }

View File

@@ -196,7 +196,7 @@ namespace CarCareTracker.Controllers
var vehicleData = _dataAccess.GetVehicleById(vehicleId); var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var userConfig = _config.GetUserConfig(User); var userConfig = _config.GetUserConfig(User);
var totalDistanceTraveled = maxMileage - minMileage; var totalDistanceTraveled = maxMileage - minMileage;
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords); var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, year, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
var viewModel = new CostTableForVehicle var viewModel = new CostTableForVehicle
{ {
ServiceRecordSum = serviceRecords.Sum(x => x.Cost), ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
@@ -349,9 +349,57 @@ namespace CarCareTracker.Controllers
var vehicleHistory = new VehicleHistoryViewModel(); var vehicleHistory = new VehicleHistoryViewModel();
vehicleHistory.ReportParameters = reportParameter; vehicleHistory.ReportParameters = reportParameter;
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId); vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleId); var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
bool useMPG = _config.GetUserConfig(User).UseMPG;
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
var gasViewModels = _gasHelper.GetGasRecordViewModels(vehicleRecords.GasRecords, useMPG, useUKMPG);
//filter by tags
if (reportParameter.Tags.Any())
{
if (reportParameter.TagFilter == TagFilter.Exclude)
{
vehicleRecords.OdometerRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.ServiceRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.CollisionRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.TaxRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
gasViewModels.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.GasRecords.RemoveAll(x => x.Tags.Any(y => reportParameter.Tags.Contains(y)));
}
else if (reportParameter.TagFilter == TagFilter.IncludeOnly)
{
vehicleRecords.OdometerRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.ServiceRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.CollisionRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.UpgradeRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.TaxRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
gasViewModels.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
vehicleRecords.GasRecords.RemoveAll(x => !x.Tags.Any(y => reportParameter.Tags.Contains(y)));
}
}
//filter by date range.
if (reportParameter.FilterByDateRange && !string.IsNullOrWhiteSpace(reportParameter.StartDate) && !string.IsNullOrWhiteSpace(reportParameter.EndDate))
{
var startDate = DateTime.Parse(reportParameter.StartDate).Date;
var endDate = DateTime.Parse(reportParameter.EndDate).Date;
//validate date range
if (endDate >= startDate) //allow for same day.
{
vehicleHistory.StartDate = reportParameter.StartDate;
vehicleHistory.EndDate = reportParameter.EndDate;
//remove all records with dates after the end date and dates before the start date.
vehicleRecords.OdometerRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.ServiceRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.CollisionRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.UpgradeRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
vehicleRecords.TaxRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
gasViewModels.RemoveAll(x => DateTime.Parse(x.Date).Date > endDate || DateTime.Parse(x.Date).Date < startDate);
vehicleRecords.GasRecords.RemoveAll(x => x.Date.Date > endDate || x.Date.Date < startDate);
}
}
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
vehicleHistory.Odometer = maxMileage.ToString("N0"); vehicleHistory.Odometer = maxMileage.ToString("N0");
var minMileage = _vehicleLogic.GetMinMileage(vehicleId); var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
var distanceTraveled = maxMileage - minMileage; var distanceTraveled = maxMileage - minMileage;
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate)) if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
{ {
@@ -388,17 +436,10 @@ namespace CarCareTracker.Controllers
} }
} }
List<GenericReportModel> reportData = new List<GenericReportModel>(); List<GenericReportModel> reportData = new List<GenericReportModel>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
bool useMPG = _config.GetUserConfig(User).UseMPG;
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit; string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km"; vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost); vehicleHistory.TotalGasCost = gasViewModels.Sum(x => x.Cost);
vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost); vehicleHistory.TotalCost = vehicleRecords.ServiceRecords.Sum(x => x.Cost) + vehicleRecords.CollisionRecords.Sum(x => x.Cost) + vehicleRecords.UpgradeRecords.Sum(x => x.Cost) + vehicleRecords.TaxRecords.Sum(x => x.Cost);
if (distanceTraveled != default) if (distanceTraveled != default)
{ {
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0"); vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0");
@@ -406,7 +447,6 @@ namespace CarCareTracker.Controllers
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled; vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
} }
var averageMPG = "0"; var averageMPG = "0";
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
if (gasViewModels.Any()) if (gasViewModels.Any())
{ {
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG); averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
@@ -425,7 +465,7 @@ namespace CarCareTracker.Controllers
} }
vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}"; vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}";
//insert servicerecords //insert servicerecords
reportData.AddRange(serviceRecords.Select(x => new GenericReportModel reportData.AddRange(vehicleRecords.ServiceRecords.Select(x => new GenericReportModel
{ {
Date = x.Date, Date = x.Date,
Odometer = x.Mileage, Odometer = x.Mileage,
@@ -433,10 +473,11 @@ namespace CarCareTracker.Controllers
Notes = x.Notes, Notes = x.Notes,
Cost = x.Cost, Cost = x.Cost,
DataType = ImportMode.ServiceRecord, DataType = ImportMode.ServiceRecord,
ExtraFields = x.ExtraFields ExtraFields = x.ExtraFields,
RequisitionHistory = x.RequisitionHistory
})); }));
//repair records //repair records
reportData.AddRange(repairRecords.Select(x => new GenericReportModel reportData.AddRange(vehicleRecords.CollisionRecords.Select(x => new GenericReportModel
{ {
Date = x.Date, Date = x.Date,
Odometer = x.Mileage, Odometer = x.Mileage,
@@ -444,9 +485,10 @@ namespace CarCareTracker.Controllers
Notes = x.Notes, Notes = x.Notes,
Cost = x.Cost, Cost = x.Cost,
DataType = ImportMode.RepairRecord, DataType = ImportMode.RepairRecord,
ExtraFields = x.ExtraFields ExtraFields = x.ExtraFields,
RequisitionHistory = x.RequisitionHistory
})); }));
reportData.AddRange(upgradeRecords.Select(x => new GenericReportModel reportData.AddRange(vehicleRecords.UpgradeRecords.Select(x => new GenericReportModel
{ {
Date = x.Date, Date = x.Date,
Odometer = x.Mileage, Odometer = x.Mileage,
@@ -454,9 +496,10 @@ namespace CarCareTracker.Controllers
Notes = x.Notes, Notes = x.Notes,
Cost = x.Cost, Cost = x.Cost,
DataType = ImportMode.UpgradeRecord, DataType = ImportMode.UpgradeRecord,
ExtraFields = x.ExtraFields ExtraFields = x.ExtraFields,
RequisitionHistory = x.RequisitionHistory
})); }));
reportData.AddRange(taxRecords.Select(x => new GenericReportModel reportData.AddRange(vehicleRecords.TaxRecords.Select(x => new GenericReportModel
{ {
Date = x.Date, Date = x.Date,
Odometer = 0, Odometer = 0,

View File

@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord) public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), serviceRecord.VehicleId))
{
return Json(false);
}
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert) if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{ {
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
} }
if (serviceRecord.DeletedRequisitionHistory.Any()) if (serviceRecord.DeletedRequisitionHistory.Any())
{ {
RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
} }
//push back any reminders //push back any reminders
if (serviceRecord.ReminderRecordId.Any()) if (serviceRecord.ReminderRecordId.Any())
@@ -61,7 +66,7 @@ namespace CarCareTracker.Controllers
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord()); var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), serviceRecord.VehicleId, User.Identity.Name, $"{(serviceRecord.Id == default ? "Created" : "Edited")} Service Record - Description: {serviceRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(serviceRecord.ToServiceRecord(), serviceRecord.Id == default ? "servicerecord.add" : "servicerecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
@@ -74,6 +79,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetServiceRecordForEditById(int serviceRecordId) public IActionResult GetServiceRecordForEditById(int serviceRecordId)
{ {
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId); var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object. //convert to Input object.
var convertedResult = new ServiceRecordInput var convertedResult = new ServiceRecordInput
{ {
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies. //restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any()) if (existingRecord.RequisitionHistory.Any())
{ {
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
} }
var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id); var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "servicerecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteServiceRecordById(int serviceRecordId) public IActionResult DeleteServiceRecordById(int serviceRecordId)
{ {
var result = DeleteServiceRecordWithChecks(serviceRecordId); var result = DeleteServiceRecordWithChecks(serviceRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
}
return Json(result); return Json(result);
} }
} }

View File

@@ -73,44 +73,7 @@ namespace CarCareTracker.Controllers
} }
return results; return results;
} }
private void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription)
{
foreach (SupplyUsageHistory supply in supplyUsage)
{
try
{
if (supply.Id == default)
{
continue; //no id, skip current supply.
}
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.Id);
if (result != null && result.Id != default)
{
//supply exists, re-add the quantity and cost
result.Quantity += supply.Quantity;
result.Cost += supply.Cost;
var requisitionRecord = new SupplyUsageHistory
{
Id = supply.Id,
Date = DateTime.Now.Date,
Description = $"Restored from {usageDescription}",
Quantity = supply.Quantity,
Cost = supply.Cost
};
result.RequisitionHistory.Add(requisitionRecord);
//save
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
}
else
{
_logger.LogError($"Unable to find supply with id {supply.Id}");
}
} catch (Exception ex)
{
_logger.LogError($"Error restoring supply with id {supply.Id} : {ex.Message}");
}
}
}
[TypeFilter(typeof(CollaboratorFilter))] [TypeFilter(typeof(CollaboratorFilter))]
[HttpGet] [HttpGet]
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId) public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
@@ -187,7 +150,7 @@ namespace CarCareTracker.Controllers
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord()); var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), supplyRecord.VehicleId, User.Identity.Name, $"{(supplyRecord.Id == default ? "Created" : "Edited")} Supply Record - Description: {supplyRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(supplyRecord.ToSupplyRecord(), supplyRecord.Id == default ? "supplyrecord.add" : "supplyrecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
@@ -236,16 +199,16 @@ namespace CarCareTracker.Controllers
} }
} }
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id); var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromSupplyRecord(existingRecord, "supplyrecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteSupplyRecordById(int supplyRecordId) public IActionResult DeleteSupplyRecordById(int supplyRecordId)
{ {
var result = DeleteSupplyRecordWithChecks(supplyRecordId); var result = DeleteSupplyRecordWithChecks(supplyRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
}
return Json(result); return Json(result);
} }
} }

View File

@@ -23,49 +23,29 @@ namespace CarCareTracker.Controllers
} }
return PartialView("_TaxRecords", result); return PartialView("_TaxRecords", result);
} }
private void UpdateRecurringTaxes(int vehicleId)
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult CheckRecurringTaxRecords(int vehicleId)
{ {
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId); try
var recurringFees = result.Where(x => x.IsRecurring);
if (recurringFees.Any())
{ {
foreach (TaxRecord recurringFee in recurringFees) var result = _vehicleLogic.UpdateRecurringTaxes(vehicleId);
{ return Json(result);
var newDate = new DateTime(); } catch (Exception ex)
if (recurringFee.RecurringInterval != ReminderMonthInterval.Other) {
{ _logger.LogError(ex.Message);
newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval); return Json(false);
}
else
{
newDate = recurringFee.Date.AddMonths(recurringFee.CustomMonthInterval);
}
if (DateTime.Now > newDate)
{
recurringFee.IsRecurring = false;
var newRecurringFee = new TaxRecord()
{
VehicleId = recurringFee.VehicleId,
Date = newDate,
Description = recurringFee.Description,
Cost = recurringFee.Cost,
IsRecurring = true,
Notes = recurringFee.Notes,
RecurringInterval = recurringFee.RecurringInterval,
CustomMonthInterval = recurringFee.CustomMonthInterval,
Files = recurringFee.Files,
Tags = recurringFee.Tags,
ExtraFields = recurringFee.ExtraFields
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
_taxRecordDataAccess.SaveTaxRecordToVehicle(newRecurringFee);
}
}
} }
} }
[HttpPost] [HttpPost]
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord) public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), taxRecord.VehicleId))
{
return Json(false);
}
//move files from temp. //move files from temp.
taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList(); taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
//push back any reminders //push back any reminders
@@ -77,9 +57,10 @@ namespace CarCareTracker.Controllers
} }
} }
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord()); var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
_vehicleLogic.UpdateRecurringTaxes(taxRecord.VehicleId);
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), taxRecord.VehicleId, User.Identity.Name, $"{(taxRecord.Id == default ? "Created" : "Edited")} Tax Record - Description: {taxRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(taxRecord.ToTaxRecord(), taxRecord.Id == default ? "taxrecord.add" : "taxrecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
@@ -92,6 +73,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetTaxRecordForEditById(int taxRecordId) public IActionResult GetTaxRecordForEditById(int taxRecordId)
{ {
var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId); var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object. //convert to Input object.
var convertedResult = new TaxRecordInput var convertedResult = new TaxRecordInput
{ {
@@ -104,6 +90,7 @@ namespace CarCareTracker.Controllers
IsRecurring = result.IsRecurring, IsRecurring = result.IsRecurring,
RecurringInterval = result.RecurringInterval, RecurringInterval = result.RecurringInterval,
CustomMonthInterval = result.CustomMonthInterval, CustomMonthInterval = result.CustomMonthInterval,
CustomMonthIntervalUnit = result.CustomMonthIntervalUnit,
Files = result.Files, Files = result.Files,
Tags = result.Tags, Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields) ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
@@ -119,16 +106,16 @@ namespace CarCareTracker.Controllers
return false; return false;
} }
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id); var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(existingRecord, "taxrecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteTaxRecordById(int taxRecordId) public IActionResult DeleteTaxRecordById(int taxRecordId)
{ {
var result = DeleteTaxRecordWithChecks(taxRecordId); var result = DeleteTaxRecordWithChecks(taxRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
}
return Json(result); return Json(result);
} }
} }

View File

@@ -26,6 +26,11 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord) public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
{ {
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), upgradeRecord.VehicleId))
{
return Json(false);
}
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert) if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{ {
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
@@ -48,7 +53,7 @@ namespace CarCareTracker.Controllers
} }
if (upgradeRecord.DeletedRequisitionHistory.Any()) if (upgradeRecord.DeletedRequisitionHistory.Any())
{ {
RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
} }
//push back any reminders //push back any reminders
if (upgradeRecord.ReminderRecordId.Any()) if (upgradeRecord.ReminderRecordId.Any())
@@ -61,7 +66,7 @@ namespace CarCareTracker.Controllers
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord()); var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), upgradeRecord.VehicleId, User.Identity.Name, $"{(upgradeRecord.Id == default ? "Created" : "Edited")} Upgrade Record - Description: {upgradeRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(upgradeRecord.ToUpgradeRecord(), upgradeRecord.Id == default ? "upgraderecord.add" : "upgraderecord.update", User.Identity.Name));
} }
return Json(result); return Json(result);
} }
@@ -74,6 +79,11 @@ namespace CarCareTracker.Controllers
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId) public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
{ {
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId); var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId))
{
return Redirect("/Error/Unauthorized");
}
//convert to Input object. //convert to Input object.
var convertedResult = new UpgradeRecordInput var convertedResult = new UpgradeRecordInput
{ {
@@ -102,19 +112,19 @@ namespace CarCareTracker.Controllers
//restore any requisitioned supplies. //restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any()) if (existingRecord.RequisitionHistory.Any())
{ {
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description); _vehicleLogic.RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
} }
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id); var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(existingRecord, "upgraderecord.delete", User.Identity.Name));
}
return result; return result;
} }
[HttpPost] [HttpPost]
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId) public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
{ {
var result = DeleteUpgradeRecordWithChecks(upgradeRecordId); var result = DeleteUpgradeRecordWithChecks(upgradeRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");
}
return Json(result); return Json(result);
} }
} }

View File

@@ -1,12 +1,12 @@
using CarCareTracker.External.Interfaces; using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
using CarCareTracker.Helper;
using System.Globalization;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using CarCareTracker.Logic;
using CarCareTracker.Filter; using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Logic;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
using System.Security.Claims;
using System.Text.Json; using System.Text.Json;
namespace CarCareTracker.Controllers namespace CarCareTracker.Controllers
@@ -95,7 +95,6 @@ namespace CarCareTracker.Controllers
public IActionResult Index(int vehicleId) public IActionResult Index(int vehicleId)
{ {
var data = _dataAccess.GetVehicleById(vehicleId); var data = _dataAccess.GetVehicleById(vehicleId);
UpdateRecurringTaxes(vehicleId);
return View(data); return View(data);
} }
[HttpGet] [HttpGet]
@@ -131,10 +130,11 @@ namespace CarCareTracker.Controllers
if (isNewAddition) if (isNewAddition)
{ {
_userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id); _userLogic.AddUserAccessToVehicle(GetUserID(), vehicleInput.Id);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Added Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Created Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.add", User.Identity.Name, vehicleInput.Id.ToString()));
} else }
else
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleInput.Id, User.Identity.Name, $"Edited Vehicle - Description: {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Updated Vehicle {vehicleInput.Year} {vehicleInput.Make} {vehicleInput.Model}({StaticHelper.GetVehicleIdentifier(vehicleInput)})", "vehicle.update", User.Identity.Name, vehicleInput.Id.ToString()));
} }
return Json(result); return Json(result);
} }
@@ -164,7 +164,7 @@ namespace CarCareTracker.Controllers
_dataAccess.DeleteVehicle(vehicleId); _dataAccess.DeleteVehicle(vehicleId);
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, "Deleted Vehicle"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic(string.Empty, "vehicle.delete", User.Identity.Name, vehicleId.ToString()));
} }
return Json(result); return Json(result);
} }
@@ -199,7 +199,7 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Failed()); return Json(OperationResponse.Failed());
} }
} }
#region "Shared Methods" #region "Shared Methods"
[HttpPost] [HttpPost]
public IActionResult GetFilesPendingUpload(List<UploadedFiles> uploadedFiles) public IActionResult GetFilesPendingUpload(List<UploadedFiles> uploadedFiles)
@@ -216,7 +216,7 @@ namespace CarCareTracker.Controllers
{ {
return Json(searchResults); return Json(searchResults);
} }
foreach(ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs) foreach (ImportMode visibleTab in _config.GetUserConfig(User).VisibleTabs)
{ {
switch (visibleTab) switch (visibleTab)
{ {
@@ -389,7 +389,7 @@ namespace CarCareTracker.Controllers
} }
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Moved multiple {source.ToString()} to {destination.ToString()} - Ids: {string.Join(",", recordIds)}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Moved multiple {source.ToString()} to {destination.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.move", User.Identity.Name, string.Empty));
} }
return Json(result); return Json(result);
} }
@@ -431,7 +431,7 @@ namespace CarCareTracker.Controllers
} }
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Deleted multiple {importMode.ToString()} - Ids: {string.Join(", ", recordIds)}", "bulk.delete", User.Identity.Name, string.Empty));
} }
return Json(result); return Json(result);
} }
@@ -490,7 +490,7 @@ namespace CarCareTracker.Controllers
} }
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Adjusted odometer for multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Adjusted odometer for multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.odometer.adjust", User.Identity.Name, string.Empty));
} }
return Json(result); return Json(result);
} }
@@ -582,7 +582,7 @@ namespace CarCareTracker.Controllers
} }
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.duplicate", User.Identity.Name, string.Empty));
} }
return Json(result); return Json(result);
} }
@@ -717,7 +717,71 @@ namespace CarCareTracker.Controllers
} }
if (result) if (result)
{ {
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)} - to Vehicle Ids: {string.Join(",", vehicleIds)}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)} - to Vehicle Ids: {string.Join(",", vehicleIds)}", "bulk.duplicate.to.vehicles", User.Identity.Name, string.Join(",", vehicleIds)));
}
return Json(result);
}
[HttpPost]
public IActionResult BulkCreateOdometerRecords(List<int> recordIds, ImportMode importMode)
{
bool result = false;
foreach (int recordId in recordIds)
{
switch (importMode)
{
case ImportMode.ServiceRecord:
{
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Service Record: {existingRecord.Description}"
});
}
break;
case ImportMode.RepairRecord:
{
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Repair Record: {existingRecord.Description}"
});
}
break;
case ImportMode.UpgradeRecord:
{
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record: {existingRecord.Description}"
});
}
break;
case ImportMode.GasRecord:
{
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
result = _odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = existingRecord.Date,
VehicleId = existingRecord.VehicleId,
Mileage = existingRecord.Mileage,
Notes = $"Auto Insert From Gas Record. {existingRecord.Notes}"
});
}
break;
}
}
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.Generic($"Created Odometer Records based on {importMode.ToString()} - Ids: {string.Join(",", recordIds)}", "bulk.odometer.insert", User.Identity.Name, string.Empty));
} }
return Json(result); return Json(result);
} }
@@ -780,14 +844,15 @@ namespace CarCareTracker.Controllers
} }
if (extraFieldIsEdited) if (extraFieldIsEdited)
{ {
foreach(ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields) foreach (ExtraField extraField in genericRecordEditModel.EditRecord.ExtraFields)
{ {
if (existingRecord.ExtraFields.Any(x=>x.Name == extraField.Name)) if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
{ {
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name); var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name); existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
existingRecord.ExtraFields.Insert(insertIndex, extraField); existingRecord.ExtraFields.Insert(insertIndex, extraField);
} else }
else
{ {
existingRecord.ExtraFields.Add(extraField); existingRecord.ExtraFields.Add(extraField);
} }
@@ -893,6 +958,167 @@ namespace CarCareTracker.Controllers
return Json(result); return Json(result);
} }
[HttpPost] [HttpPost]
public IActionResult PrintRecordStickers(int vehicleId, List<int> recordIds, ImportMode importMode)
{
bool result = false;
if (!recordIds.Any())
{
return Json(result);
}
var stickerViewModel = new StickerViewModel() { RecordType = importMode };
if (vehicleId != default)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
if (vehicleData != null && vehicleData.Id != default)
{
stickerViewModel.VehicleData = vehicleData;
}
}
int recordsAdded = 0;
switch (importMode)
{
case ImportMode.ServiceRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.GenericRecords.Add(_serviceRecordDataAccess.GetServiceRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.RepairRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.GenericRecords.Add(_collisionRecordDataAccess.GetCollisionRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.UpgradeRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.GenericRecords.Add(_upgradeRecordDataAccess.GetUpgradeRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.GasRecord:
{
foreach (int recordId in recordIds)
{
var record = _gasRecordDataAccess.GetGasRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Cost = record.Cost,
Date = record.Date,
Notes = record.Notes,
Mileage = record.Mileage,
ExtraFields = record.ExtraFields
});
recordsAdded++;
}
}
break;
case ImportMode.TaxRecord:
{
foreach (int recordId in recordIds)
{
var record = _taxRecordDataAccess.GetTaxRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Description = record.Description,
Cost = record.Cost,
Notes = record.Notes,
Date = record.Date,
ExtraFields = record.ExtraFields
});
recordsAdded++;
}
}
break;
case ImportMode.SupplyRecord:
{
foreach (int recordId in recordIds)
{
var record = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
stickerViewModel.SupplyRecords.Add(record);
recordsAdded++;
}
}
break;
case ImportMode.NoteRecord:
{
foreach (int recordId in recordIds)
{
var record = _noteDataAccess.GetNoteById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Description = record.Description,
Notes = record.NoteText
});
recordsAdded++;
}
}
break;
case ImportMode.OdometerRecord:
{
foreach (int recordId in recordIds)
{
var record = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Date = record.Date,
Mileage = record.Mileage,
Notes = record.Notes,
ExtraFields = record.ExtraFields
});
recordsAdded++;
}
}
break;
case ImportMode.ReminderRecord:
{
foreach (int recordId in recordIds)
{
stickerViewModel.ReminderRecords.Add(_reminderRecordDataAccess.GetReminderRecordById(recordId));
recordsAdded++;
}
}
break;
case ImportMode.PlanRecord:
{
foreach (int recordId in recordIds)
{
var record = _planRecordDataAccess.GetPlanRecordById(recordId);
stickerViewModel.GenericRecords.Add(new GenericRecord
{
Description = record.Description,
Cost = record.Cost,
Notes = record.Notes,
Date = record.DateModified,
ExtraFields = record.ExtraFields,
RequisitionHistory = record.RequisitionHistory
});
recordsAdded++;
}
}
break;
}
if (recordsAdded > 0)
{
return PartialView("_Stickers", stickerViewModel);
}
return Json(result);
}
[HttpPost]
public IActionResult SaveUserColumnPreferences(UserColumnPreference columnPreference) public IActionResult SaveUserColumnPreferences(UserColumnPreference columnPreference)
{ {
try try
@@ -903,6 +1129,7 @@ namespace CarCareTracker.Controllers
{ {
var existingPreference = existingUserColumnPreference.Single(); var existingPreference = existingUserColumnPreference.Single();
existingPreference.VisibleColumns = columnPreference.VisibleColumns; existingPreference.VisibleColumns = columnPreference.VisibleColumns;
existingPreference.ColumnOrder = columnPreference.ColumnOrder;
} }
else else
{ {

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public enum ReminderIntervalUnit
{
Months = 1,
Days = 2
}
}

8
Enum/TagFilter.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public enum TagFilter
{
Exclude = 0,
IncludeOnly = 1
}
}

View File

@@ -19,27 +19,34 @@ namespace CarCareTracker.Filter
{ {
if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser))) if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser)))
{ {
var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString()); if (filterContext.ActionArguments.ContainsKey("vehicleId"))
if (vehicleId != default)
{ {
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)); var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString());
if (!_userLogic.UserCanEditVehicle(userId, vehicleId)) if (vehicleId != default)
{ {
filterContext.Result = new RedirectResult("/Error/Unauthorized"); var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
{
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
}
else
{
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" };
if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
{
//user trying to access shop supplies but shop supplies is not enabled by root user.
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
{
//user trying to access any other endpoints using 0 as vehicle id.
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
} }
} else } else
{ {
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" }; filterContext.Result = new RedirectResult("/Error/Unauthorized");
if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
{
//user trying to access shop supplies but shop supplies is not enabled by root user.
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
{
//user trying to access any other endpoints using 0 as vehicle id.
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
} }
} }
} }

View File

@@ -24,7 +24,9 @@ namespace CarCareTracker.Helper
bool GetServerEnableShopSupplies(); bool GetServerEnableShopSupplies();
string GetServerPostgresConnection(); string GetServerPostgresConnection();
string GetAllowedFileUploadExtensions(); string GetAllowedFileUploadExtensions();
public bool DeleteUserConfig(int userId); bool DeleteUserConfig(int userId);
bool GetInvariantApi();
bool GetServerOpenRegistration();
} }
public class ConfigHelper : IConfigHelper public class ConfigHelper : IConfigHelper
{ {
@@ -51,11 +53,19 @@ namespace CarCareTracker.Helper
{ {
return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS")); return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS"));
} }
public bool GetInvariantApi()
{
return CheckBool(CheckString("LUBELOGGER_INVARIANT_API"));
}
public string GetMOTD() public string GetMOTD()
{ {
var motd = CheckString("LUBELOGGER_MOTD"); var motd = CheckString("LUBELOGGER_MOTD");
return motd; return motd;
} }
public bool GetServerOpenRegistration()
{
return CheckBool(CheckString("LUBELOGGER_OPEN_REGISTRATION"));
}
public OpenIDConfig GetOpenIDConfig() public OpenIDConfig GetOpenIDConfig()
{ {
OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig(); OpenIDConfig openIdConfig = _config.GetSection("OpenIDConfig").Get<OpenIDConfig>() ?? new OpenIDConfig();
@@ -224,6 +234,7 @@ namespace CarCareTracker.Helper
EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))), EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))),
PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)), PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)),
PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)), PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)),
UseUnitForFuelCost = CheckBool(CheckString(nameof(UserConfig.UseUnitForFuelCost))),
UserLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US"), UserLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US"),
HideSoldVehicles = CheckBool(CheckString(nameof(UserConfig.HideSoldVehicles))), HideSoldVehicles = CheckBool(CheckString(nameof(UserConfig.HideSoldVehicles))),
EnableShopSupplies = CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies))), EnableShopSupplies = CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies))),

View File

@@ -6,6 +6,7 @@ namespace CarCareTracker.Helper
public interface IFileHelper public interface IFileHelper
{ {
string GetFullFilePath(string currentFilePath, bool mustExist = true); string GetFullFilePath(string currentFilePath, bool mustExist = true);
byte[] GetFileBytes(string fullFilePath, bool deleteFile = false);
string MoveFileFromTemp(string currentFilePath, string newFolder); string MoveFileFromTemp(string currentFilePath, string newFolder);
bool RenameFile(string currentFilePath, string newName); bool RenameFile(string currentFilePath, string newName);
bool DeleteFile(string currentFilePath); bool DeleteFile(string currentFilePath);
@@ -34,7 +35,7 @@ namespace CarCareTracker.Helper
} }
public List<string> GetLanguages() public List<string> GetLanguages()
{ {
var languagePath = Path.Combine(_webEnv.WebRootPath, "translations"); var languagePath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
var defaultList = new List<string>() { "en_US" }; var defaultList = new List<string>() { "en_US" };
if (Directory.Exists(languagePath)) if (Directory.Exists(languagePath))
{ {
@@ -72,7 +73,7 @@ namespace CarCareTracker.Helper
{ {
currentFilePath = currentFilePath.Substring(1); currentFilePath = currentFilePath.Substring(1);
} }
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath); string oldFilePath = currentFilePath.StartsWith("defaults/") ? Path.Combine(_webEnv.WebRootPath, currentFilePath) : Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
if (File.Exists(oldFilePath)) if (File.Exists(oldFilePath))
{ {
return oldFilePath; return oldFilePath;
@@ -85,6 +86,19 @@ namespace CarCareTracker.Helper
return string.Empty; return string.Empty;
} }
} }
public byte[] GetFileBytes(string fullFilePath, bool deleteFile = false)
{
if (File.Exists(fullFilePath))
{
var fileBytes = File.ReadAllBytes(fullFilePath);
if (deleteFile)
{
File.Delete(fullFilePath);
}
return fileBytes;
}
return Array.Empty<byte>();
}
public bool RestoreBackup(string fileName, bool clearExisting = false) public bool RestoreBackup(string fileName, bool clearExisting = false)
{ {
var fullFilePath = GetFullFilePath(fileName); var fullFilePath = GetFullFilePath(fileName);
@@ -94,7 +108,7 @@ namespace CarCareTracker.Helper
} }
try try
{ {
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{Guid.NewGuid()}"); var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{Guid.NewGuid()}");
if (!Directory.Exists(tempPath)) if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath); Directory.CreateDirectory(tempPath);
//extract zip file //extract zip file
@@ -105,10 +119,10 @@ namespace CarCareTracker.Helper
var translationPath = Path.Combine(tempPath, "translations"); var translationPath = Path.Combine(tempPath, "translations");
var dataPath = Path.Combine(tempPath, StaticHelper.DbName); var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath); var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath); var configPath = Path.Combine(tempPath, StaticHelper.LegacyUserConfigPath);
if (Directory.Exists(imagePath)) if (Directory.Exists(imagePath))
{ {
var existingPath = Path.Combine(_webEnv.WebRootPath, "images"); var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
if (!Directory.Exists(existingPath)) if (!Directory.Exists(existingPath))
{ {
Directory.CreateDirectory(existingPath); Directory.CreateDirectory(existingPath);
@@ -130,7 +144,7 @@ namespace CarCareTracker.Helper
} }
if (Directory.Exists(documentPath)) if (Directory.Exists(documentPath))
{ {
var existingPath = Path.Combine(_webEnv.WebRootPath, "documents"); var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
if (!Directory.Exists(existingPath)) if (!Directory.Exists(existingPath))
{ {
Directory.CreateDirectory(existingPath); Directory.CreateDirectory(existingPath);
@@ -152,7 +166,7 @@ namespace CarCareTracker.Helper
} }
if (Directory.Exists(translationPath)) if (Directory.Exists(translationPath))
{ {
var existingPath = Path.Combine(_webEnv.WebRootPath, "translations"); var existingPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
if (!Directory.Exists(existingPath)) if (!Directory.Exists(existingPath))
{ {
Directory.CreateDirectory(existingPath); Directory.CreateDirectory(existingPath);
@@ -186,9 +200,9 @@ namespace CarCareTracker.Helper
if (File.Exists(configPath)) if (File.Exists(configPath))
{ {
//check if config folder exists. //check if config folder exists.
if (!Directory.Exists("config/")) if (!Directory.Exists("data/config"))
{ {
Directory.CreateDirectory("config/"); Directory.CreateDirectory("data/config");
} }
File.Move(configPath, StaticHelper.UserConfigPath, true); File.Move(configPath, StaticHelper.UserConfigPath, true);
} }
@@ -203,7 +217,7 @@ namespace CarCareTracker.Helper
public string MakeAttachmentsExport(List<GenericReportModel> exportData) public string MakeAttachmentsExport(List<GenericReportModel> exportData)
{ {
var folderName = Guid.NewGuid(); var folderName = Guid.NewGuid();
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}"); var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
if (!Directory.Exists(tempPath)) if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath); Directory.CreateDirectory(tempPath);
int fileIndex = 0; int fileIndex = 0;
@@ -227,10 +241,10 @@ namespace CarCareTracker.Helper
public string MakeBackup() public string MakeBackup()
{ {
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}"; var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}"); var tempPath = Path.Combine(_webEnv.ContentRootPath, "data", $"temp/{folderName}");
var imagePath = Path.Combine(_webEnv.WebRootPath, "images"); var imagePath = Path.Combine(_webEnv.ContentRootPath, "data", "images");
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents"); var documentPath = Path.Combine(_webEnv.ContentRootPath, "data", "documents");
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations"); var translationPath = Path.Combine(_webEnv.ContentRootPath, "data", "translations");
var dataPath = StaticHelper.DbName; var dataPath = StaticHelper.DbName;
var widgetPath = StaticHelper.AdditionalWidgetsPath; var widgetPath = StaticHelper.AdditionalWidgetsPath;
var configPath = StaticHelper.UserConfigPath; var configPath = StaticHelper.UserConfigPath;
@@ -301,8 +315,8 @@ namespace CarCareTracker.Helper
{ {
currentFilePath = currentFilePath.Substring(1); currentFilePath = currentFilePath.Substring(1);
} }
string uploadPath = Path.Combine(_webEnv.WebRootPath, newFolder); string uploadPath = Path.Combine(_webEnv.ContentRootPath, "data", newFolder);
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath); string oldFilePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
if (!Directory.Exists(uploadPath)) if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath); Directory.CreateDirectory(uploadPath);
string newFileUploadPath = oldFilePath.Replace(tempPath, newFolder); string newFileUploadPath = oldFilePath.Replace(tempPath, newFolder);
@@ -319,7 +333,7 @@ namespace CarCareTracker.Helper
{ {
currentFilePath = currentFilePath.Substring(1); currentFilePath = currentFilePath.Substring(1);
} }
string filePath = Path.Combine(_webEnv.WebRootPath, currentFilePath); string filePath = Path.Combine(_webEnv.ContentRootPath, "data", currentFilePath);
if (File.Exists(filePath)) if (File.Exists(filePath))
{ {
File.Delete(filePath); File.Delete(filePath);

View File

@@ -76,7 +76,8 @@ namespace CarCareTracker.Helper
MissedFuelUp = currentObject.MissedFuelUp, MissedFuelUp = currentObject.MissedFuelUp,
Notes = currentObject.Notes, Notes = currentObject.Notes,
Tags = currentObject.Tags, Tags = currentObject.Tags,
ExtraFields = currentObject.ExtraFields ExtraFields = currentObject.ExtraFields,
Files = currentObject.Files
}; };
if (currentObject.MissedFuelUp) if (currentObject.MissedFuelUp)
{ {
@@ -86,9 +87,9 @@ namespace CarCareTracker.Helper
unFactoredConsumption = 0; unFactoredConsumption = 0;
unFactoredMileage = 0; unFactoredMileage = 0;
} }
else if (currentObject.IsFillToFull) else if (currentObject.IsFillToFull && currentObject.Mileage != default)
{ {
//if user filled to full. //if user filled to full and an odometer is provided, otherwise we will defer calculations
if (convertedConsumption > 0.00M && deltaMileage > 0) if (convertedConsumption > 0.00M && deltaMileage > 0)
{ {
try try
@@ -130,10 +131,14 @@ namespace CarCareTracker.Helper
MissedFuelUp = currentObject.MissedFuelUp, MissedFuelUp = currentObject.MissedFuelUp,
Notes = currentObject.Notes, Notes = currentObject.Notes,
Tags = currentObject.Tags, Tags = currentObject.Tags,
ExtraFields = currentObject.ExtraFields ExtraFields = currentObject.ExtraFields,
Files = currentObject.Files
}); });
} }
previousMileage = currentObject.Mileage; if (currentObject.Mileage != default)
{
previousMileage = currentObject.Mileage;
}
} }
return computedResults; return computedResults;
} }

View File

@@ -15,16 +15,21 @@ namespace CarCareTracker.Helper
public class MailHelper : IMailHelper public class MailHelper : IMailHelper
{ {
private readonly MailConfig mailConfig; private readonly MailConfig mailConfig;
private readonly string serverLanguage;
private readonly IFileHelper _fileHelper; private readonly IFileHelper _fileHelper;
private readonly ITranslationHelper _translator;
private readonly ILogger<MailHelper> _logger; private readonly ILogger<MailHelper> _logger;
public MailHelper( public MailHelper(
IConfigHelper config, IConfigHelper config,
IFileHelper fileHelper, IFileHelper fileHelper,
ITranslationHelper translationHelper,
ILogger<MailHelper> logger ILogger<MailHelper> logger
) { ) {
//load mailConfig from Configuration //load mailConfig from Configuration
mailConfig = config.GetMailConfig(); mailConfig = config.GetMailConfig();
serverLanguage = config.GetServerLanguage();
_fileHelper = fileHelper; _fileHelper = fileHelper;
_translator = translationHelper;
_logger = logger; _logger = logger;
} }
public OperationResponse NotifyUserForRegistration(string emailAddress, string token) public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
@@ -36,8 +41,8 @@ namespace CarCareTracker.Helper
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) { if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
return OperationResponse.Failed("Email Address or Token is invalid"); return OperationResponse.Failed("Email Address or Token is invalid");
} }
string emailSubject = "Your Registration Token for LubeLogger"; string emailSubject = _translator.Translate(serverLanguage, "Your Registration Token for LubeLogger");
string emailBody = $"A token has been generated on your behalf, please complete your registration for LubeLogger using the token: {token}"; string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please complete your registration for LubeLogger using the token")}: {token}";
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody); var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result) if (result)
{ {
@@ -57,8 +62,8 @@ namespace CarCareTracker.Helper
{ {
return OperationResponse.Failed("Email Address or Token is invalid"); return OperationResponse.Failed("Email Address or Token is invalid");
} }
string emailSubject = "Your Password Reset Token for LubeLogger"; string emailSubject = _translator.Translate(serverLanguage, "Your Password Reset Token for LubeLogger");
string emailBody = $"A token has been generated on your behalf, please reset your password for LubeLogger using the token: {token}"; string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please reset your password for LubeLogger using the token")}: {token}";
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody); var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result) if (result)
{ {
@@ -79,8 +84,8 @@ namespace CarCareTracker.Helper
{ {
return OperationResponse.Failed("Email Address or Token is invalid"); return OperationResponse.Failed("Email Address or Token is invalid");
} }
string emailSubject = "Your User Account Update Token for LubeLogger"; string emailSubject = _translator.Translate(serverLanguage, "Your User Account Update Token for LubeLogger");
string emailBody = $"A token has been generated on your behalf, please update your account for LubeLogger using the token: {token}"; string emailBody = $"{_translator.Translate(serverLanguage, "A token has been generated on your behalf, please update your account for LubeLogger using the token")}: {token}";
var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody); var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody);
if (result) if (result)
{ {
@@ -107,17 +112,18 @@ namespace CarCareTracker.Helper
} }
//get email template, this file has to exist since it's a static file. //get email template, this file has to exist since it's a static file.
var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate); var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate);
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}"; string emailSubject = $"{_translator.Translate(serverLanguage, "Vehicle Reminders From LubeLogger")} - {DateTime.Now.ToShortDateString()}";
//construct html table. //construct html table.
string emailBody = File.ReadAllText(emailTemplatePath); string emailBody = File.ReadAllText(emailTemplatePath);
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}"); emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}");
string tableHeader = $"<th>{_translator.Translate(serverLanguage, "Urgency")}</th><th>{_translator.Translate(serverLanguage, "Description")}</th><th>{_translator.Translate(serverLanguage, "Due")}</th>";
string tableBody = ""; string tableBody = "";
foreach(ReminderRecordViewModel reminder in reminders) foreach(ReminderRecordViewModel reminder in reminders)
{ {
var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date.ToShortDateString()} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}"; var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date.ToShortDateString()} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}";
tableBody += $"<tr class='{reminder.Urgency}'><td>{StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency)}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>"; tableBody += $"<tr class='{reminder.Urgency}'><td>{_translator.Translate(serverLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
} }
emailBody = emailBody.Replace("{TableBody}", tableBody); emailBody = emailBody.Replace("{TableHeader}", tableHeader).Replace("{TableBody}", tableBody);
try try
{ {
var result = SendEmail(emailAddresses, emailSubject, emailBody); var result = SendEmail(emailAddresses, emailSubject, emailBody);

View File

@@ -25,7 +25,14 @@ namespace CarCareTracker.Helper
existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval); existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval);
} else } else
{ {
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval); if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
{
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
}
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
{
existingReminder.Date = newDate.Date.AddDays(existingReminder.CustomMonthInterval);
}
} }
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other) if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
@@ -55,7 +62,14 @@ namespace CarCareTracker.Helper
} }
else else
{ {
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval); if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Months)
{
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
}
else if (existingReminder.CustomMonthIntervalUnit == ReminderIntervalUnit.Days)
{
existingReminder.Date = newDate.AddDays(existingReminder.CustomMonthInterval);
}
} }
} }
return existingReminder; return existingReminder;

View File

@@ -1,6 +1,9 @@
using CarCareTracker.Models; using CarCareTracker.Models;
using CsvHelper; using CsvHelper;
using System.Globalization; using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace CarCareTracker.Helper namespace CarCareTracker.Helper
{ {
@@ -9,9 +12,10 @@ namespace CarCareTracker.Helper
/// </summary> /// </summary>
public static class StaticHelper public static class StaticHelper
{ {
public const string VersionNumber = "1.4.1"; public const string VersionNumber = "1.4.5";
public const string DbName = "data/cartracker.db"; public const string DbName = "data/cartracker.db";
public const string UserConfigPath = "config/userConfig.json"; public const string UserConfigPath = "data/config/userConfig.json";
public const string LegacyUserConfigPath = "config/userConfig.json";
public const string AdditionalWidgetsPath = "data/widgets.html"; public const string AdditionalWidgetsPath = "data/widgets.html";
public const string GenericErrorMessage = "An error occurred, please try again later"; public const string GenericErrorMessage = "An error occurred, please try again later";
public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt"; public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
@@ -239,7 +243,8 @@ namespace CarCareTracker.Helper
public static List<ExtraField> AddExtraFields(List<ExtraField> recordExtraFields, List<ExtraField> templateExtraFields) public static List<ExtraField> AddExtraFields(List<ExtraField> recordExtraFields, List<ExtraField> templateExtraFields)
{ {
if (!templateExtraFields.Any()) { if (!templateExtraFields.Any())
{
return new List<ExtraField>(); return new List<ExtraField>();
} }
if (!recordExtraFields.Any()) if (!recordExtraFields.Any())
@@ -260,7 +265,7 @@ namespace CarCareTracker.Helper
extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired; extraField.IsRequired = templateExtraFields.Where(x => x.Name == extraField.Name).First().IsRequired;
} }
//append extra fields //append extra fields
foreach(ExtraField extraField in templateExtraFields) foreach (ExtraField extraField in templateExtraFields)
{ {
if (!recordFieldNames.Contains(extraField.Name)) if (!recordFieldNames.Contains(extraField.Name))
{ {
@@ -308,7 +313,8 @@ namespace CarCareTracker.Helper
if (mailConfig != null && !string.IsNullOrWhiteSpace(mailConfig.EmailServer)) if (mailConfig != null && !string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{ {
Console.WriteLine($"SMTP Configured for {mailConfig.EmailServer}"); Console.WriteLine($"SMTP Configured for {mailConfig.EmailServer}");
} else }
else
{ {
Console.WriteLine("SMTP Not Configured"); Console.WriteLine("SMTP Not Configured");
} }
@@ -316,23 +322,113 @@ namespace CarCareTracker.Helper
Console.WriteLine($"Message Of The Day: {motd}"); Console.WriteLine($"Message Of The Day: {motd}");
if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name)) if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name))
{ {
Console.WriteLine("No Locale or Culture Configured for LubeLogger, Check Environment Variables"); Console.WriteLine("WARNING: No Locale or Culture Configured for LubeLogger, Check Environment Variables");
}
//Create folders if they don't exist.
if (!Directory.Exists("data"))
{
Directory.CreateDirectory("data");
Console.WriteLine("Created data directory");
}
if (!Directory.Exists("data/images"))
{
Console.WriteLine("Created images directory");
Directory.CreateDirectory("data/images");
}
if (!Directory.Exists("data/documents"))
{
Directory.CreateDirectory("data/documents");
Console.WriteLine("Created documents directory");
}
if (!Directory.Exists("data/translations"))
{
Directory.CreateDirectory("data/translations");
Console.WriteLine("Created translations directory");
}
if (!Directory.Exists("data/temp"))
{
Directory.CreateDirectory("data/temp");
Console.WriteLine("Created translations directory");
}
if (!Directory.Exists("data/config"))
{
Directory.CreateDirectory("data/config");
Console.WriteLine("Created config directory");
} }
} }
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action) public static void CheckMigration(string webRootPath, string webContentPath)
{
//check if current working directory differs from content root.
if (Directory.GetCurrentDirectory() != webContentPath)
{
Console.WriteLine("WARNING: The Working Directory differs from the Web Content Path");
Console.WriteLine($"Working Directory: {Directory.GetCurrentDirectory()}");
Console.WriteLine($"Web Content Path: {webContentPath}");
}
//migrates all user-uploaded files from webroot to new data folder
//images
var imagePath = Path.Combine(webRootPath, "images");
var docsPath = Path.Combine(webRootPath, "documents");
var translationPath = Path.Combine(webRootPath, "translations");
var tempPath = Path.Combine(webRootPath, "temp");
if (File.Exists(LegacyUserConfigPath))
{
File.Move(LegacyUserConfigPath, UserConfigPath, true);
}
if (Directory.Exists(imagePath))
{
foreach (string fileToMove in Directory.GetFiles(imagePath))
{
var newFilePath = $"data/images/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Image: {Path.GetFileName(fileToMove)}");
}
}
if (Directory.Exists(docsPath))
{
foreach (string fileToMove in Directory.GetFiles(docsPath))
{
var newFilePath = $"data/documents/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Document: {Path.GetFileName(fileToMove)}");
}
}
if (Directory.Exists(translationPath))
{
foreach (string fileToMove in Directory.GetFiles(translationPath))
{
var newFilePath = $"data/translations/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Translation: {Path.GetFileName(fileToMove)}");
}
}
if (Directory.Exists(tempPath))
{
foreach (string fileToMove in Directory.GetFiles(tempPath))
{
var newFilePath = $"data/temp/{Path.GetFileName(fileToMove)}";
File.Move(fileToMove, newFilePath, true);
Console.WriteLine($"Migrated Temp File: {Path.GetFileName(fileToMove)}");
}
}
}
public static async void NotifyAsync(string webhookURL, WebHookPayload webHookPayload)
{ {
if (string.IsNullOrWhiteSpace(webhookURL)) if (string.IsNullOrWhiteSpace(webhookURL))
{ {
return; return;
} }
var httpClient = new HttpClient(); var httpClient = new HttpClient();
var httpParams = new Dictionary<string, string> if (webhookURL.StartsWith("discord://"))
{ {
{ "vehicleId", vehicleId.ToString() }, webhookURL = webhookURL.Replace("discord://", "https://"); //cleanurl
{ "username", username }, //format to discord
{ "action", action }, httpClient.PostAsJsonAsync(webhookURL, DiscordWebHook.FromWebHookPayload(webHookPayload));
}; }
httpClient.PostAsJsonAsync(webhookURL, httpParams); else
{
httpClient.PostAsJsonAsync(webhookURL, webHookPayload);
}
} }
public static string GetImportModeIcon(ImportMode importMode) public static string GetImportModeIcon(ImportMode importMode)
{ {
@@ -367,12 +463,14 @@ namespace CarCareTracker.Helper
if (vehicle.VehicleIdentifier == "LicensePlate") if (vehicle.VehicleIdentifier == "LicensePlate")
{ {
return vehicle.LicensePlate; return vehicle.LicensePlate;
} else }
else
{ {
if (vehicle.ExtraFields.Any(x=>x.Name == vehicle.VehicleIdentifier)) if (vehicle.ExtraFields.Any(x => x.Name == vehicle.VehicleIdentifier))
{ {
return vehicle.ExtraFields?.FirstOrDefault(x=>x.Name == vehicle.VehicleIdentifier)?.Value; return vehicle.ExtraFields?.FirstOrDefault(x => x.Name == vehicle.VehicleIdentifier)?.Value;
} else }
else
{ {
return "N/A"; return "N/A";
} }
@@ -399,10 +497,11 @@ namespace CarCareTracker.Helper
//Translations //Translations
public static string GetTranslationDownloadPath(string continent, string name) public static string GetTranslationDownloadPath(string continent, string name)
{ {
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name)){ if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name))
{
return string.Empty; return string.Empty;
} }
else else
{ {
switch (continent) switch (continent)
{ {
@@ -421,8 +520,9 @@ namespace CarCareTracker.Helper
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
{ {
return string.Empty; return string.Empty;
} else }
{ else
{
try try
{ {
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name; string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
@@ -435,7 +535,8 @@ namespace CarCareTracker.Helper
{ {
return displayName; return displayName;
} }
} catch (Exception ex) }
catch (Exception ex)
{ {
return name; return name;
} }
@@ -606,7 +707,8 @@ namespace CarCareTracker.Helper
if (input == 0M.ToString("C2") && hideZero) if (input == 0M.ToString("C2") && hideZero)
{ {
return "---"; return "---";
} else }
else
{ {
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}"; return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
} }
@@ -622,6 +724,13 @@ namespace CarCareTracker.Helper
return string.IsNullOrWhiteSpace(decorations) ? input.ToString("C2") : $"{input.ToString("C2")}{decorations}"; return string.IsNullOrWhiteSpace(decorations) ? input.ToString("C2") : $"{input.ToString("C2")}{decorations}";
} }
} }
public static JsonSerializerOptions GetInvariantOption()
{
var serializerOption = new JsonSerializerOptions();
serializerOption.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
serializerOption.Converters.Add(new InvariantConverter());
return serializerOption;
}
public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords) public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords)
{ {
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct(); var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
@@ -659,5 +768,52 @@ namespace CarCareTracker.Helper
_csv.NextRecord(); _csv.NextRecord();
} }
} }
public static byte[] RemindersToCalendar(List<ReminderRecordViewModel> reminders)
{
//converts reminders to iCal file
StringBuilder sb = new StringBuilder();
//start the calendar item
sb.AppendLine("BEGIN:VCALENDAR");
sb.AppendLine("VERSION:2.0");
sb.AppendLine("PRODID:lubelogger.com");
sb.AppendLine("CALSCALE:GREGORIAN");
sb.AppendLine("METHOD:PUBLISH");
//create events.
foreach(ReminderRecordViewModel reminder in reminders)
{
var dtStart = reminder.Date.Date.ToString("yyyyMMddTHHmm00");
var dtEnd = reminder.Date.Date.AddDays(1).AddMilliseconds(-1).ToString("yyyyMMddTHHmm00");
var calendarUID = new Guid(MD5.HashData(Encoding.UTF8.GetBytes($"{dtStart}_{reminder.Description}")));
sb.AppendLine("BEGIN:VEVENT");
sb.AppendLine("DTSTAMP:" + DateTime.Now.ToString("yyyyMMddTHHmm00"));
sb.AppendLine("UID:" + calendarUID);
sb.AppendLine("DTSTART:" + dtStart);
sb.AppendLine("DTEND:" + dtEnd);
sb.AppendLine($"SUMMARY:{reminder.Description}");
sb.AppendLine($"DESCRIPTION:{reminder.Description}");
switch (reminder.Urgency)
{
case ReminderUrgency.NotUrgent:
sb.AppendLine("PRIORITY:3");
break;
case ReminderUrgency.Urgent:
sb.AppendLine("PRIORITY:2");
break;
case ReminderUrgency.VeryUrgent:
sb.AppendLine("PRIORITY:1");
break;
case ReminderUrgency.PastDue:
sb.AppendLine("PRIORITY:1");
break;
}
sb.AppendLine("END:VEVENT");
}
//end calendar item
sb.AppendLine("END:VCALENDAR");
string calendarContent = sb.ToString();
return Encoding.UTF8.GetBytes(calendarContent);
}
} }
} }

View File

@@ -21,6 +21,7 @@ namespace CarCareTracker.Logic
OperationResponse RequestResetPassword(LoginModel credentials); OperationResponse RequestResetPassword(LoginModel credentials);
OperationResponse ResetPasswordByUser(LoginModel credentials); OperationResponse ResetPasswordByUser(LoginModel credentials);
OperationResponse ResetUserPassword(LoginModel credentials); OperationResponse ResetUserPassword(LoginModel credentials);
OperationResponse SendRegistrationToken(LoginModel credentials);
UserData ValidateUserCredentials(LoginModel credentials); UserData ValidateUserCredentials(LoginModel credentials);
UserData ValidateOpenIDUser(LoginModel credentials); UserData ValidateOpenIDUser(LoginModel credentials);
bool CheckIfUserIsValid(int userId); bool CheckIfUserIsValid(int userId);
@@ -190,6 +191,16 @@ namespace CarCareTracker.Logic
return OperationResponse.Failed(); return OperationResponse.Failed();
} }
} }
public OperationResponse SendRegistrationToken(LoginModel credentials)
{
if (_configHelper.GetServerOpenRegistration())
{
return GenerateUserToken(credentials.EmailAddress, true);
} else
{
return OperationResponse.Failed("Open Registration Disabled");
}
}
/// <summary> /// <summary>
/// Generates a token and notifies user via email so they can reset their password. /// Generates a token and notifies user via email so they can reset their password.
/// </summary> /// </summary>
@@ -310,7 +321,18 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress); var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
if (existingToken.Id != default) if (existingToken.Id != default)
{ {
return OperationResponse.Failed("There is an existing token tied to this email address"); if (autoNotify) //re-send email
{
var notificationResult = _mailHelper.NotifyUserForRegistration(emailAddress, existingToken.Body);
if (notificationResult.Success)
{
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}, a new email has been sent out");
} else
{
return notificationResult;
}
}
return OperationResponse.Failed($"There is an existing token tied to {emailAddress}");
} }
var token = new Token() var token = new Token()
{ {

View File

@@ -1,4 +1,5 @@
using CarCareTracker.External.Interfaces; using CarCareTracker.Controllers;
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper; using CarCareTracker.Helper;
using CarCareTracker.Models; using CarCareTracker.Models;
@@ -12,11 +13,13 @@ namespace CarCareTracker.Logic
int GetMaxMileage(VehicleRecords vehicleRecords); int GetMaxMileage(VehicleRecords vehicleRecords);
int GetMinMileage(int vehicleId); int GetMinMileage(int vehicleId);
int GetMinMileage(VehicleRecords vehicleRecords); int GetMinMileage(VehicleRecords vehicleRecords);
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords); int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage); bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles); List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar); List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone); List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
bool UpdateRecurringTaxes(int vehicleId);
void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription);
} }
public class VehicleLogic: IVehicleLogic public class VehicleLogic: IVehicleLogic
{ {
@@ -29,6 +32,10 @@ namespace CarCareTracker.Logic
private readonly IReminderRecordDataAccess _reminderRecordDataAccess; private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IPlanRecordDataAccess _planRecordDataAccess; private readonly IPlanRecordDataAccess _planRecordDataAccess;
private readonly IReminderHelper _reminderHelper; private readonly IReminderHelper _reminderHelper;
private readonly IVehicleDataAccess _dataAccess;
private readonly ISupplyRecordDataAccess _supplyRecordDataAccess;
private readonly ILogger<VehicleLogic> _logger;
public VehicleLogic( public VehicleLogic(
IServiceRecordDataAccess serviceRecordDataAccess, IServiceRecordDataAccess serviceRecordDataAccess,
IGasRecordDataAccess gasRecordDataAccess, IGasRecordDataAccess gasRecordDataAccess,
@@ -38,7 +45,10 @@ namespace CarCareTracker.Logic
IOdometerRecordDataAccess odometerRecordDataAccess, IOdometerRecordDataAccess odometerRecordDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess, IReminderRecordDataAccess reminderRecordDataAccess,
IPlanRecordDataAccess planRecordDataAccess, IPlanRecordDataAccess planRecordDataAccess,
IReminderHelper reminderHelper IReminderHelper reminderHelper,
IVehicleDataAccess dataAccess,
ISupplyRecordDataAccess supplyRecordDataAccess,
ILogger<VehicleLogic> logger
) { ) {
_serviceRecordDataAccess = serviceRecordDataAccess; _serviceRecordDataAccess = serviceRecordDataAccess;
_gasRecordDataAccess = gasRecordDataAccess; _gasRecordDataAccess = gasRecordDataAccess;
@@ -49,6 +59,9 @@ namespace CarCareTracker.Logic
_planRecordDataAccess = planRecordDataAccess; _planRecordDataAccess = planRecordDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess; _reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper; _reminderHelper = reminderHelper;
_dataAccess = dataAccess;
_supplyRecordDataAccess = supplyRecordDataAccess;
_logger = logger;
} }
public VehicleRecords GetVehicleRecords(int vehicleId) public VehicleRecords GetVehicleRecords(int vehicleId)
{ {
@@ -186,22 +199,44 @@ namespace CarCareTracker.Logic
} }
return numbersArray.Any() ? numbersArray.Min() : 0; return numbersArray.Any() ? numbersArray.Min() : 0;
} }
public int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords) public int GetOwnershipDays(string purchaseDate, string soldDate, int year, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
{ {
var startDate = DateTime.Now; var startDate = DateTime.Now;
var endDate = DateTime.Now; var endDate = DateTime.Now;
if (!string.IsNullOrWhiteSpace(soldDate)) bool usePurchaseDate = false;
bool useSoldDate = false;
if (!string.IsNullOrWhiteSpace(soldDate) && DateTime.TryParse(soldDate, out DateTime vehicleSoldDate))
{ {
endDate = DateTime.Parse(soldDate); if (year == default || year >= vehicleSoldDate.Year) //All Time is selected or the selected year is greater or equal to the year the vehicle is sold
{
endDate = vehicleSoldDate; //cap end date to vehicle sold date.
useSoldDate = true;
}
} }
if (!string.IsNullOrWhiteSpace(purchaseDate)) if (!string.IsNullOrWhiteSpace(purchaseDate) && DateTime.TryParse(purchaseDate, out DateTime vehiclePurchaseDate))
{ {
//if purchase date is provided, then we just have to subtract the begin date to end date and return number of months if (year == default || year <= vehiclePurchaseDate.Year) //All Time is selected or the selected year is less or equal to the year the vehicle is purchased
startDate = DateTime.Parse(purchaseDate); {
startDate = vehiclePurchaseDate; //cap start date to vehicle purchase date
usePurchaseDate = true;
}
}
if (year != default)
{
var calendarYearStart = new DateTime(year, 1, 1);
var calendarYearEnd = new DateTime(year + 1, 1, 1);
if (!useSoldDate)
{
endDate = endDate > calendarYearEnd ? calendarYearEnd : endDate;
}
if (!usePurchaseDate)
{
startDate = startDate > calendarYearStart ? calendarYearStart : startDate;
}
var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays); var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays);
return timeElapsed; return timeElapsed;
} }
var dateArray = new List<DateTime>(); var dateArray = new List<DateTime>() { startDate };
dateArray.AddRange(serviceRecords.Select(x => x.Date)); dateArray.AddRange(serviceRecords.Select(x => x.Date));
dateArray.AddRange(repairRecords.Select(x => x.Date)); dateArray.AddRange(repairRecords.Select(x => x.Date));
dateArray.AddRange(gasRecords.Select(x => x.Date)); dateArray.AddRange(gasRecords.Select(x => x.Date));
@@ -317,5 +352,98 @@ namespace CarCareTracker.Logic
} }
return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList(); return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList();
} }
public bool UpdateRecurringTaxes(int vehicleId)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
if (!string.IsNullOrWhiteSpace(vehicleData.SoldDate))
{
return false;
}
bool RecurringTaxIsOutdated(TaxRecord taxRecord)
{
var monthInterval = taxRecord.RecurringInterval != ReminderMonthInterval.Other ? (int)taxRecord.RecurringInterval : taxRecord.CustomMonthInterval;
bool addDays = taxRecord.RecurringInterval == ReminderMonthInterval.Other && taxRecord.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
return addDays ? DateTime.Now > taxRecord.Date.AddDays(monthInterval) : DateTime.Now > taxRecord.Date.AddMonths(monthInterval);
}
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var outdatedRecurringFees = result.Where(x => x.IsRecurring && RecurringTaxIsOutdated(x));
if (outdatedRecurringFees.Any())
{
var success = false;
foreach (TaxRecord recurringFee in outdatedRecurringFees)
{
var monthInterval = recurringFee.RecurringInterval != ReminderMonthInterval.Other ? (int)recurringFee.RecurringInterval : recurringFee.CustomMonthInterval;
bool isOutdated = true;
bool addDays = recurringFee.RecurringInterval == ReminderMonthInterval.Other && recurringFee.CustomMonthIntervalUnit == ReminderIntervalUnit.Days;
//update the original outdated tax record
recurringFee.IsRecurring = false;
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
//month multiplier for severely outdated monthly tax records.
int monthMultiplier = 1;
var originalDate = recurringFee.Date;
while (isOutdated)
{
try
{
var nextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
monthMultiplier++;
var nextnextDate = addDays ? originalDate.AddDays(monthInterval * monthMultiplier) : originalDate.AddMonths(monthInterval * monthMultiplier);
recurringFee.Date = nextDate;
recurringFee.Id = default; //new record
recurringFee.IsRecurring = DateTime.Now <= nextnextDate;
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
isOutdated = !recurringFee.IsRecurring;
success = true;
}
catch (Exception)
{
isOutdated = false; //break out of loop if something broke.
success = false;
}
}
}
return success;
}
return false; //no outdated recurring tax records.
}
public void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription)
{
foreach (SupplyUsageHistory supply in supplyUsage)
{
try
{
if (supply.Id == default)
{
continue; //no id, skip current supply.
}
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.Id);
if (result != null && result.Id != default)
{
//supply exists, re-add the quantity and cost
result.Quantity += supply.Quantity;
result.Cost += supply.Cost;
var requisitionRecord = new SupplyUsageHistory
{
Id = supply.Id,
Date = DateTime.Now.Date,
Description = $"Restored from {usageDescription}",
Quantity = supply.Quantity,
Cost = supply.Cost
};
result.RequisitionHistory.Add(requisitionRecord);
//save
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
}
else
{
_logger.LogError($"Unable to find supply with id {supply.Id}");
}
}
catch (Exception ex)
{
_logger.LogError($"Error restoring supply with id {supply.Id} : {ex.Message}");
}
}
}
} }
} }

View File

@@ -75,7 +75,9 @@ namespace CarCareTracker.Middleware
var userIdentity = new List<Claim> var userIdentity = new List<Claim>
{ {
new(ClaimTypes.Name, splitString[0]), new(ClaimTypes.Name, splitString[0]),
new(ClaimTypes.NameIdentifier, userData.Id.ToString()) new(ClaimTypes.NameIdentifier, userData.Id.ToString()),
new(ClaimTypes.Email, userData.EmailAddress),
new(ClaimTypes.Role, "APIAuth")
}; };
if (userData.IsAdmin) if (userData.IsAdmin)
{ {
@@ -154,6 +156,7 @@ namespace CarCareTracker.Middleware
if (value.ToString().ToLower() == "api") if (value.ToString().ToLower() == "api")
{ {
Response.StatusCode = 401; Response.StatusCode = 401;
Response.Headers.Append("WWW-Authenticate", "Basic");
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

138
Models/API/TypeConverter.cs Normal file
View File

@@ -0,0 +1,138 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CarCareTracker.Models
{
public class DummyType
{
}
class InvariantConverter : JsonConverter<DummyType>
{
public override void Write(Utf8JsonWriter writer, DummyType value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override DummyType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
class FromDateOptional: JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var tokenType = reader.TokenType;
if (tokenType == JsonTokenType.String)
{
return reader.GetString();
}
else if (tokenType == JsonTokenType.Number)
{
if (reader.TryGetInt64(out long intInput))
{
return DateTimeOffset.FromUnixTimeSeconds(intInput).Date.ToShortDateString();
}
}
return reader.GetString();
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
{
writer.WriteStringValue(DateTime.Parse(value).ToString("yyyy-MM-dd"));
}
else
{
writer.WriteStringValue(value);
}
}
}
class FromDecimalOptional : JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var tokenType = reader.TokenType;
if (tokenType == JsonTokenType.String)
{
return reader.GetString();
}
else if (tokenType == JsonTokenType.Number) {
if (reader.TryGetDecimal(out decimal decimalInput))
{
return decimalInput.ToString();
}
}
return reader.GetString();
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
if (options.Converters.Any(x=>x.Type == typeof(DummyType)))
{
writer.WriteNumberValue(decimal.Parse(value));
} else
{
writer.WriteStringValue(value);
}
}
}
class FromIntOptional : JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var tokenType = reader.TokenType;
if (tokenType == JsonTokenType.String)
{
return reader.GetString();
}
else if (tokenType == JsonTokenType.Number)
{
if (reader.TryGetInt32(out int intInput))
{
return intInput.ToString();
}
}
return reader.GetString();
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
{
writer.WriteNumberValue(int.Parse(value));
}
else
{
writer.WriteStringValue(value);
}
}
}
class FromBoolOptional : JsonConverter<string>
{
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var tokenType = reader.TokenType;
switch (tokenType)
{
case JsonTokenType.String:
return reader.GetString();
case JsonTokenType.True:
return "True";
case JsonTokenType.False:
return "False";
default:
return reader.GetString();
}
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
if (options.Converters.Any(x => x.Type == typeof(DummyType)))
{
writer.WriteBooleanValue(bool.Parse(value));
}
else
{
writer.WriteStringValue(value);
}
}
}
}

View File

@@ -23,6 +23,7 @@
public string Notes { get; set; } public string Notes { get; set; }
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>(); public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp); } } public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public bool IncludeInAverage { get { return MilesPerGallon > 0 || (!IsFillToFull && !MissedFuelUp) || (Mileage == default && !MissedFuelUp); } }
} }
} }

View File

@@ -13,6 +13,7 @@
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig(); public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
public int CustomMileageInterval { get; set; } = 0; public int CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0; public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles; public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear; public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
public ReminderMetric Metric { get; set; } = ReminderMetric.Date; public ReminderMetric Metric { get; set; } = ReminderMetric.Date;

View File

@@ -13,6 +13,7 @@
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig(); public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
public int CustomMileageInterval { get; set; } = 0; public int CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0; public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles; public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear; public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
public ReminderMetric Metric { get; set; } = ReminderMetric.Date; public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
@@ -34,6 +35,7 @@
ReminderMonthInterval = ReminderMonthInterval, ReminderMonthInterval = ReminderMonthInterval,
CustomMileageInterval = CustomMileageInterval, CustomMileageInterval = CustomMileageInterval,
CustomMonthInterval = CustomMonthInterval, CustomMonthInterval = CustomMonthInterval,
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
Notes = Notes, Notes = Notes,
Tags = Tags Tags = Tags
}; };

View File

@@ -13,5 +13,6 @@
public decimal Cost { get; set; } public decimal Cost { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>(); public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>(); public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
} }
} }

View File

@@ -2,7 +2,13 @@
{ {
public class ReportParameter public class ReportParameter
{ {
public List<string> VisibleColumns { get; set; } = new List<string>(); public List<string> VisibleColumns { get; set; } = new List<string>();
public List<string> ExtraFields { get; set; } = new List<string>(); public List<string> ExtraFields { get; set; } = new List<string>();
public TagFilter TagFilter { get; set; } = TagFilter.Exclude;
public List<string> Tags { get; set; } = new List<string>();
public bool FilterByDateRange { get; set; } = false;
public string StartDate { get; set; } = "";
public string EndDate { get; set; } = "";
public bool PrintIndividualRecords { get; set; } = false;
} }
} }

View File

@@ -17,5 +17,7 @@
public decimal TotalDepreciation { get; set; } public decimal TotalDepreciation { get; set; }
public decimal DepreciationPerDay { get; set; } public decimal DepreciationPerDay { get; set; }
public decimal DepreciationPerMile { get; set; } public decimal DepreciationPerMile { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
} }
} }

View File

@@ -1,4 +1,6 @@
namespace CarCareTracker.Models using System.Text.Json.Serialization;
namespace CarCareTracker.Models
{ {
/// <summary> /// <summary>
/// Import model used for importing records via CSV. /// Import model used for importing records via CSV.
@@ -41,48 +43,75 @@
public string Cost { get; set; } public string Cost { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public string Tags { get; set; } public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
} }
public class GenericRecordExportModel public class GenericRecordExportModel
{ {
[JsonConverter(typeof(FromIntOptional))]
public string Id { get; set; }
[JsonConverter(typeof(FromDateOptional))]
public string Date { get; set; } public string Date { get; set; }
[JsonConverter(typeof(FromIntOptional))]
public string Odometer { get; set; } public string Odometer { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
[JsonConverter(typeof(FromDecimalOptional))]
public string Cost { get; set; } public string Cost { get; set; }
public string Tags { get; set; } public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
} }
public class OdometerRecordExportModel public class OdometerRecordExportModel
{ {
[JsonConverter(typeof(FromIntOptional))]
public string Id { get; set; }
[JsonConverter(typeof(FromDateOptional))]
public string Date { get; set; } public string Date { get; set; }
[JsonConverter(typeof(FromIntOptional))]
public string InitialOdometer { get; set; } public string InitialOdometer { get; set; }
[JsonConverter(typeof(FromIntOptional))]
public string Odometer { get; set; } public string Odometer { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public string Tags { get; set; } public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
} }
public class TaxRecordExportModel public class TaxRecordExportModel
{ {
[JsonConverter(typeof(FromIntOptional))]
public string Id { get; set; }
[JsonConverter(typeof(FromDateOptional))]
public string Date { get; set; } public string Date { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
[JsonConverter(typeof(FromDecimalOptional))]
public string Cost { get; set; } public string Cost { get; set; }
public string Tags { get; set; } public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
} }
public class GasRecordExportModel public class GasRecordExportModel
{ {
[JsonConverter(typeof(FromIntOptional))]
public string Id { get; set; }
[JsonConverter(typeof(FromDateOptional))]
public string Date { get; set; } public string Date { get; set; }
[JsonConverter(typeof(FromIntOptional))]
public string Odometer { get; set; } public string Odometer { get; set; }
[JsonConverter(typeof(FromDecimalOptional))]
public string FuelConsumed { get; set; } public string FuelConsumed { get; set; }
[JsonConverter(typeof(FromDecimalOptional))]
public string Cost { get; set; } public string Cost { get; set; }
[JsonConverter(typeof(FromDecimalOptional))]
public string FuelEconomy { get; set; } public string FuelEconomy { get; set; }
[JsonConverter(typeof(FromBoolOptional))]
public string IsFillToFull { get; set; } public string IsFillToFull { get; set; }
[JsonConverter(typeof(FromBoolOptional))]
public string MissedFuelUp { get; set; } public string MissedFuelUp { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public string Tags { get; set; } public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; } public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
} }
public class ReminderExportModel public class ReminderExportModel
{ {
@@ -90,19 +119,36 @@
public string Urgency { get; set; } public string Urgency { get; set; }
public string Metric { get; set; } public string Metric { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
[JsonConverter(typeof(FromDateOptional))]
public string DueDate { get; set; } public string DueDate { get; set; }
[JsonConverter(typeof(FromIntOptional))]
public string DueOdometer { get; set; } public string DueOdometer { get; set; }
} }
public class PlanRecordExportModel public class PlanRecordExportModel
{ {
[JsonConverter(typeof(FromIntOptional))]
public string Id { get; set; }
[JsonConverter(typeof(FromDateOptional))]
public string DateCreated { get; set; } public string DateCreated { get; set; }
[JsonConverter(typeof(FromDateOptional))]
public string DateModified { get; set; } public string DateModified { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string Priority { get; set; } public string Priority { get; set; }
public string Progress { get; set; } public string Progress { get; set; }
[JsonConverter(typeof(FromDecimalOptional))]
public string Cost { get; set; } public string Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; } public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
public class UserExportModel
{
public string Username { get; set; }
public string EmailAddress { get; set; }
[JsonConverter(typeof(FromBoolOptional))]
public string IsAdmin { get; set; }
[JsonConverter(typeof(FromBoolOptional))]
public string IsRoot { get; set; }
} }
} }

View File

@@ -0,0 +1,11 @@
namespace CarCareTracker.Models
{
public class StickerViewModel
{
public ImportMode RecordType { get; set; }
public Vehicle VehicleData { get; set; } = new Vehicle();
public List<ReminderRecord> ReminderRecords { get; set; } = new List<ReminderRecord>();
public List<GenericRecord> GenericRecords { get; set; } = new List<GenericRecord>();
public List<SupplyRecord> SupplyRecords { get; set; } = new List<SupplyRecord>();
}
}

View File

@@ -0,0 +1,253 @@
using System.Text.Json.Serialization;
namespace CarCareTracker.Models
{
/// <summary>
/// WebHookPayload Object
/// </summary>
public class WebHookPayloadBase
{
public string Type { get; set; } = "";
public string Timestamp
{
get { return DateTime.UtcNow.ToString("O"); }
}
public Dictionary<string, string> Data { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Legacy attributes below
/// </summary>
public string VehicleId { get; set; } = "";
public string Username { get; set; } = "";
public string Action { get; set; } = "";
}
public class DiscordWebHook
{
public string Username { get { return "LubeLogger"; } }
[JsonPropertyName("avatar_url")]
public string AvatarUrl { get { return "https://hargata.github.io/hargata/lubelogger_logo_small.png"; } }
public string Content { get; set; } = "";
public static DiscordWebHook FromWebHookPayload(WebHookPayload webHookPayload)
{
return new DiscordWebHook
{
Content = webHookPayload.Action,
};
}
}
public class WebHookPayload: WebHookPayloadBase
{
private static string GetFriendlyActionType(string actionType)
{
var actionTypeParts = actionType.Split('.');
if (actionTypeParts.Length == 2)
{
var recordType = actionTypeParts[0];
var recordAction = actionTypeParts[1];
switch (recordAction)
{
case "add":
recordAction = "Added";
break;
case "update":
recordAction = "Updated";
break;
case "delete":
recordAction = "Deleted";
break;
}
if (recordType.ToLower().Contains("record"))
{
var cleanedRecordType = recordType.ToLower().Replace("record", "");
cleanedRecordType = $"{char.ToUpper(cleanedRecordType[0])}{cleanedRecordType.Substring(1)} Record";
recordType = cleanedRecordType;
} else
{
recordType = $"{char.ToUpper(recordType[0])}{recordType.Substring(1)}";
}
return $"{recordAction} {recordType}";
} else if (actionTypeParts.Length == 3)
{
var recordType = actionTypeParts[0];
var recordAction = actionTypeParts[1];
var thirdPart = actionTypeParts[2];
switch (recordAction)
{
case "add":
recordAction = "Added";
break;
case "update":
recordAction = "Updated";
break;
case "delete":
recordAction = "Deleted";
break;
}
if (recordType.ToLower().Contains("record"))
{
var cleanedRecordType = recordType.ToLower().Replace("record", "");
cleanedRecordType = $"{char.ToUpper(cleanedRecordType[0])}{cleanedRecordType.Substring(1)} Record";
recordType = cleanedRecordType;
}
else
{
recordType = $"{char.ToUpper(recordType[0])}{recordType.Substring(1)}";
}
if (thirdPart == "api")
{
return $"{recordAction} {recordType} via API";
} else
{
return $"{recordAction} {recordType}";
}
}
return actionType;
}
public static WebHookPayload FromGenericRecord(GenericRecord genericRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", genericRecord.Description);
payloadDictionary.Add("odometer", genericRecord.Mileage.ToString());
payloadDictionary.Add("vehicleId", genericRecord.VehicleId.ToString());
payloadDictionary.Add("cost", genericRecord.Cost.ToString("F2"));
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = genericRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {genericRecord.Description}"
};
}
public static WebHookPayload FromGasRecord(GasRecord gasRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("odometer", gasRecord.Mileage.ToString());
payloadDictionary.Add("fuelconsumed", gasRecord.Gallons.ToString());
payloadDictionary.Add("vehicleId", gasRecord.VehicleId.ToString());
payloadDictionary.Add("cost", gasRecord.Cost.ToString("F2"));
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = gasRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Odometer: {gasRecord.Mileage}"
};
}
public static WebHookPayload FromOdometerRecord(OdometerRecord odometerRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("initialodometer", odometerRecord.InitialMileage.ToString());
payloadDictionary.Add("odometer", odometerRecord.Mileage.ToString());
payloadDictionary.Add("vehicleId", odometerRecord.VehicleId.ToString());
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = odometerRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Odometer: {odometerRecord.Mileage}"
};
}
public static WebHookPayload FromTaxRecord(TaxRecord taxRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", taxRecord.Description);
payloadDictionary.Add("vehicleId", taxRecord.VehicleId.ToString());
payloadDictionary.Add("cost", taxRecord.Cost.ToString("F2"));
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = taxRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {taxRecord.Description}"
};
}
public static WebHookPayload FromPlanRecord(PlanRecord planRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", planRecord.Description);
payloadDictionary.Add("vehicleId", planRecord.VehicleId.ToString());
payloadDictionary.Add("cost", planRecord.Cost.ToString("F2"));
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = planRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {planRecord.Description}"
};
}
public static WebHookPayload FromReminderRecord(ReminderRecord reminderRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", reminderRecord.Description);
payloadDictionary.Add("vehicleId", reminderRecord.VehicleId.ToString());
payloadDictionary.Add("metric", reminderRecord.Metric.ToString());
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = reminderRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {reminderRecord.Description}"
};
}
public static WebHookPayload FromSupplyRecord(SupplyRecord supplyRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", supplyRecord.Description);
payloadDictionary.Add("vehicleId", supplyRecord.VehicleId.ToString());
payloadDictionary.Add("cost", supplyRecord.Cost.ToString("F2"));
payloadDictionary.Add("quantity", supplyRecord.Quantity.ToString("F2"));
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = supplyRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {supplyRecord.Description}"
};
}
public static WebHookPayload FromNoteRecord(Note noteRecord, string actionType, string userName)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
payloadDictionary.Add("description", noteRecord.Description);
payloadDictionary.Add("vehicleId", noteRecord.VehicleId.ToString());
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = noteRecord.VehicleId.ToString(),
Username = userName,
Action = $"{userName} {GetFriendlyActionType(actionType)} Description: {noteRecord.Description}"
};
}
public static WebHookPayload Generic(string payload, string actionType, string userName, string vehicleId)
{
Dictionary<string, string> payloadDictionary = new Dictionary<string, string>();
payloadDictionary.Add("user", userName);
if (!string.IsNullOrWhiteSpace(payload))
{
payloadDictionary.Add("description", payload);
}
return new WebHookPayload
{
Type = actionType,
Data = payloadDictionary,
VehicleId = string.IsNullOrWhiteSpace(vehicleId) ? "N/A" : vehicleId,
Username = userName,
Action = string.IsNullOrWhiteSpace(payload) ? $"{userName} {GetFriendlyActionType(actionType)}" : $"{userName} {payload}"
};
}
}
}

View File

@@ -11,6 +11,7 @@
public bool IsRecurring { get; set; } = false; public bool IsRecurring { get; set; } = false;
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear; public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
public int CustomMonthInterval { get; set; } = 0; public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>(); public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>(); public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();

View File

@@ -12,6 +12,7 @@
public bool IsRecurring { get; set; } = false; public bool IsRecurring { get; set; } = false;
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths; public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
public int CustomMonthInterval { get; set; } = 0; public int CustomMonthInterval { get; set; } = 0;
public ReminderIntervalUnit CustomMonthIntervalUnit { get; set; } = ReminderIntervalUnit.Months;
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>(); public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>(); public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
@@ -25,6 +26,7 @@
IsRecurring = IsRecurring, IsRecurring = IsRecurring,
RecurringInterval = RecurringInterval, RecurringInterval = RecurringInterval,
CustomMonthInterval = CustomMonthInterval, CustomMonthInterval = CustomMonthInterval,
CustomMonthIntervalUnit = CustomMonthIntervalUnit,
Files = Files, Files = Files,
Tags = Tags, Tags = Tags,
ExtraFields = ExtraFields ExtraFields = ExtraFields

View File

@@ -4,5 +4,6 @@
{ {
public ImportMode Tab { get; set; } public ImportMode Tab { get; set; }
public List<string> VisibleColumns { get; set; } = new List<string>(); public List<string> VisibleColumns { get; set; } = new List<string>();
public List<string> ColumnOrder { get; set; } = new List<string>();
} }
} }

View File

@@ -23,6 +23,7 @@
public bool AutomaticDecimalFormat { get; set; } public bool AutomaticDecimalFormat { get; set; }
public string PreferredGasUnit { get; set; } = string.Empty; public string PreferredGasUnit { get; set; } = string.Empty;
public string PreferredGasMileageUnit { get; set; } = string.Empty; public string PreferredGasMileageUnit { get; set; } = string.Empty;
public bool UseUnitForFuelCost { get; set; }
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>(); public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig(); public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig();
public string UserNameHash { get; set; } public string UserNameHash { get; set; }

View File

@@ -7,11 +7,14 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
//Print Messages //Print Messages
StaticHelper.InitMessage(builder.Configuration); StaticHelper.InitMessage(builder.Configuration);
//Check Migration
StaticHelper.CheckMigration(builder.Environment.WebRootPath, builder.Environment.ContentRootPath);
// Add services to the container. // Add services to the container.
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews();
@@ -66,8 +69,8 @@ builder.Services.AddSingleton<IGasHelper, GasHelper>();
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>(); builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
builder.Services.AddSingleton<IReportHelper, ReportHelper>(); builder.Services.AddSingleton<IReportHelper, ReportHelper>();
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>(); builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>(); builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
//configure logic //configure logic
builder.Services.AddSingleton<ILoginLogic, LoginLogic>(); builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
@@ -75,15 +78,6 @@ builder.Services.AddSingleton<IUserLogic, UserLogic>();
builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>(); builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>();
builder.Services.AddSingleton<IVehicleLogic, VehicleLogic>(); builder.Services.AddSingleton<IVehicleLogic, VehicleLogic>();
if (!Directory.Exists("data"))
{
Directory.CreateDirectory("data");
}
if (!Directory.Exists("config"))
{
Directory.CreateDirectory("config");
}
//Additional JsonFile //Additional JsonFile
builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true); builder.Configuration.AddJsonFile(StaticHelper.UserConfigPath, optional: true, reloadOnChange: true);
@@ -112,13 +106,57 @@ var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
app.UseExceptionHandler("/Home/Error"); app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions app.UseStaticFiles(new StaticFileOptions
{ {
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "images")),
RequestPath = "/images",
OnPrepareResponse = ctx => OnPrepareResponse = ctx =>
{ {
if (ctx.Context.Request.Path.StartsWithSegments("/images") || ctx.Context.Request.Path.StartsWithSegments("/documents")) if (ctx.Context.Request.Path.StartsWithSegments("/images"))
{ {
ctx.Context.Response.Headers.Add("Cache-Control", "no-store"); ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated)
{
ctx.Context.Response.Redirect("/Login");
}
}
}
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "documents")),
RequestPath = "/documents",
OnPrepareResponse = ctx =>
{
if (ctx.Context.Request.Path.StartsWithSegments("/documents"))
{
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated)
{
ctx.Context.Response.Redirect("/Login");
}
}
}
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "translations")),
RequestPath = "/translations"
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data", "temp")),
RequestPath = "/temp",
OnPrepareResponse = ctx =>
{
if (ctx.Context.Request.Path.StartsWithSegments("/temp"))
{
ctx.Context.Response.Headers.Append("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated) if (!ctx.Context.User.Identity.IsAuthenticated)
{ {
ctx.Context.Response.Redirect("/Login"); ctx.Context.Response.Redirect("/Login");

View File

@@ -28,9 +28,23 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <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/vehicles</code> <code>/api/vehicles</code>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -42,9 +56,9 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable testable">
<code>/api/vehicle/info</code> <code>/api/vehicle/info</code>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -56,7 +70,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/adjustedodometer</code> <code>/api/vehicle/adjustedodometer</code>
@@ -72,7 +86,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/odometerrecords</code> <code>/api/vehicle/odometerrecords</code>
@@ -86,7 +100,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/latest</code> <code>/api/vehicle/odometerrecords/latest</code>
@@ -100,7 +114,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST <span class="badge bg-primary">POST</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/add</code> <code>/api/vehicle/odometerrecords/add</code>
@@ -118,12 +132,127 @@
notes - notes(optional)<br /> notes - notes(optional)<br />
tags - tags separated by space(optional)<br /> tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br /> extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
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> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <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>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/servicerecords</code> <code>/api/vehicle/servicerecords</code>
@@ -137,7 +266,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST <span class="badge bg-primary">POST</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/servicerecords/add</code> <code>/api/vehicle/servicerecords/add</code>
@@ -156,12 +285,51 @@
notes - notes(optional)<br /> notes - notes(optional)<br />
tags - tags separated by space(optional)<br /> tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br /> extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
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> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <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>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/repairrecords</code> <code>/api/vehicle/repairrecords</code>
@@ -175,7 +343,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST <span class="badge bg-primary">POST</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/repairrecords/add</code> <code>/api/vehicle/repairrecords/add</code>
@@ -194,12 +362,51 @@
notes - notes(optional)<br /> notes - notes(optional)<br />
tags - tags separated by space(optional)<br /> tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br /> extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
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> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <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>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/upgraderecords</code> <code>/api/vehicle/upgraderecords</code>
@@ -213,7 +420,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST <span class="badge bg-primary">POST</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/upgraderecords/add</code> <code>/api/vehicle/upgraderecords/add</code>
@@ -232,12 +439,51 @@
notes - notes(optional)<br /> notes - notes(optional)<br />
tags - tags separated by space(optional)<br /> tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br /> extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
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> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <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>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/taxrecords</code> <code>/api/vehicle/taxrecords</code>
@@ -251,7 +497,21 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST <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>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/taxrecords/add</code> <code>/api/vehicle/taxrecords/add</code>
@@ -269,12 +529,50 @@
notes - notes(optional)<br /> notes - notes(optional)<br />
tags - tags separated by space(optional)<br /> tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br /> extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
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> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <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>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/gasrecords</code> <code>/api/vehicle/gasrecords</code>
@@ -292,7 +590,7 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST <span class="badge bg-primary">POST</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/gasrecords/add</code> <code>/api/vehicle/gasrecords/add</code>
@@ -313,12 +611,53 @@
notes - notes(optional)<br /> notes - notes(optional)<br />
tags - tags separated by space(optional)<br /> tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br /> extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
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> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <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>
<div class="col-5 copyable"> <div class="col-5 copyable">
<code>/api/vehicle/reminders</code> <code>/api/vehicle/reminders</code>
@@ -330,13 +669,43 @@
vehicleId - Id of Vehicle vehicleId - Id of Vehicle
</div> </div>
</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))) @if (User.IsInRole(nameof(UserData.IsRootUser)))
{ {
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable testable">
<code>/api/vehicle/reminders/send</code> <code>/api/vehicle/reminders/send</code>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -344,14 +713,14 @@
</div> </div>
<div class="col-3"> <div class="col-3">
(must be root user)<br /> (must be root user)<br />
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue] urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue](optional)
</div> </div>
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable testable">
<code>/api/makebackup</code> <code>/api/makebackup</code>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -363,9 +732,9 @@
</div> </div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET <span class="badge bg-success">GET</span>
</div> </div>
<div class="col-5 copyable"> <div class="col-5 copyable testable">
<code>/api/cleanup</code> <code>/api/cleanup</code>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -373,13 +742,20 @@
</div> </div>
<div class="col-3"> <div class="col-3">
(must be root user)<br /> (must be root user)<br />
deepClean(bool) - Perform deep clean deepClean(bool) - Perform deep clean(optional)
</div> </div>
</div> </div>
} }
<script> <script>
$('.copyable').on('click', function (e) { $('.copyable').on('click', function (e) {
copyToClipboard(e.currentTarget); if (e.ctrlKey || e.metaKey){
let targetElement = $(e.currentTarget);
if (targetElement.hasClass("testable")){
window.location = targetElement.text().trim();
}
} else {
copyToClipboard(e.currentTarget);
}
}) })
function showExtraFieldsInfo(){ function showExtraFieldsInfo(){
Swal.fire({ Swal.fire({
@@ -388,4 +764,11 @@
icon: "info" 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> </script>

View File

@@ -33,7 +33,7 @@
<li class="nav-item" role="presentation"> <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> <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> </li>
@if (User.IsInRole("CookieAuth")) @if (User.IsInRole("CookieAuth") || User.IsInRole("APIAuth"))
{ {
@if (User.IsInRole(nameof(UserData.IsAdmin))) @if (User.IsInRole(nameof(UserData.IsAdmin)))
{ {
@@ -84,7 +84,7 @@
<li class="nav-item ms-auto" role="presentation"> <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 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Settings")</span></button> <button class="nav-link resizable-nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Settings")</span></button>
</li> </li>
@if (User.IsInRole("CookieAuth")) @if (User.IsInRole("CookieAuth") || User.IsInRole("APIAuth"))
{ {
<li class="nav-item dropdown" role="presentation"> <li class="nav-item dropdown" 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 d-sm-none d-md-inline">@User.Identity.Name</span></a> <a class="nav-link resizable-nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person"></i><span class="ms-2 d-sm-none d-md-inline">@User.Identity.Name</span></a>
@@ -146,6 +146,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="stickerPrintContainer hideOnPrint">
</div>
<script> <script>
loadGarage(); loadGarage();
bindWindowResize(); bindWindowResize();

View File

@@ -4,6 +4,7 @@
@model KioskViewModel @model KioskViewModel
@section Scripts { @section Scripts {
<script src="~/lib/masonry/masonry.min.js"></script> <script src="~/lib/masonry/masonry.min.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
} }
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px"> <div class="progress" 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 class="progress-bar" style="width: 0%"></div>
@@ -123,9 +124,15 @@
} }
function toggleReminderNote(sender){ function toggleReminderNote(sender){
var reminderNote = $(sender).find('.reminder-note'); var reminderNote = $(sender).find('.reminder-note');
if (reminderNote.text().trim() != ''){ var reminderNoteText = reminderNote.text().trim();
if (reminderNoteText != ''){
if (reminderNote.hasClass('d-none')) { if (reminderNote.hasClass('d-none')) {
reminderNote.removeClass('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 { } else {
reminderNote.addClass('d-none'); reminderNote.addClass('d-none');
} }

View File

@@ -10,10 +10,10 @@
@if (recordTags.Any()) @if (recordTags.Any())
{ {
<div class='row'> <div class='row'>
<div class="d-flex align-items-center flex-wrap mt-4"> <div class="col-12 d-flex align-items-center flex-wrap mt-2 mb-2">
@foreach (string recordTag in recordTags) @foreach (string recordTag in recordTags)
{ {
<span onclick="filterGarage(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span> <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"> <datalist id="tagList">
@foreach (string recordTag in recordTags) @foreach (string recordTag in recordTags)
@@ -24,13 +24,12 @@
</div> </div>
</div> </div>
} }
<div class="row"> <div class="row gy-3 align-items-stretch vehiclesContainer pb-2 @(recordTags.Any() ? "" : "mt-2")">
<div class="row gy-3 align-items-stretch vehiclesContainer">
@foreach (VehicleViewModel vehicle in Model) @foreach (VehicleViewModel vehicle in Model)
{ {
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate))) @if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
{ {
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)"> <div class="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)"> <div class="card" onclick="viewVehicle(@vehicle.Id)">
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" /> <img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate)) @if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
@@ -82,10 +81,9 @@
</div> </div>
} }
} }
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 garage-item-add"> <div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-6 garage-item-add">
<div class="card" onclick="showAddVehicleModal()" style="height:100%;"> <div class="card" onclick="showAddVehicleModal()" style="height:100%;">
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" /> <img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
</div> </div>
</div> </div>
</div> </div>
</div>

View File

@@ -47,9 +47,15 @@
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label> <label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
</div> </div>
</div> </div>
<div class="form-check form-switch"> <div>
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost"> <div class="form-check form-switch form-check-inline">
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label> <input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
</div>
<div class="form-check form-switch 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>
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption"> <input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimalGasConsumption" checked="@Model.UserConfig.UseThreeDecimalGasConsumption">

View File

@@ -16,7 +16,7 @@
<img src="@config.GetLogoUrl()" class="lubelogger-logo" /> <img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<div class="form-group"> <div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label> <label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
<input type="text" id="inputUserName" class="form-control"> <input type="text" onkeyup="callBackOnEnter(event, requestPasswordReset)" id="inputUserName" class="form-control">
</div> </div>
<div class="d-grid"> <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> <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>

View File

@@ -24,7 +24,7 @@
<div class="form-group"> <div class="form-group">
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label> <label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
<div class="input-group"> <div class="input-group">
<input type="password" id="inputUserPassword" onkeyup="handlePasswordKeyPress(event)" class="form-control"> <input type="password" id="inputUserPassword" onkeyup="callBackOnEnter(event, performLogin)" class="form-control">
<div class="input-group-text"> <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> <button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
</div> </div>

View File

@@ -3,6 +3,7 @@
@inject ITranslationHelper translator @inject ITranslationHelper translator
@{ @{
var userLanguage = config.GetServerLanguage(); var userLanguage = config.GetServerLanguage();
var openRegistrationEnabled = config.GetServerOpenRegistration();
} }
@model string @model string
@{ @{
@@ -17,11 +18,23 @@
<img src="@config.GetLogoUrl()" class="lubelogger-logo" /> <img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<div class="form-group"> <div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label> <label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
<input type="text" id="inputToken" class="form-control"> @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>
<div class="form-group"> <div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label> <label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
<input type="text" id="inputUserName" class="form-control" value="@Model"> <input type="text" id="inputUserName" class="form-control" value="@Model" onkeyup="callBackOnEnter(event, performOpenIdRegistration)">
</div> </div>
<div class="d-grid"> <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> <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>
@@ -46,4 +59,14 @@
} }
}); });
} }
function sendOpenIdRegistrationToken(){
var userEmail = decodeHTMLEntities('@Model');
$.post('/Login/SendRegistrationToken', { emailAddress: userEmail }, function (data) {
if (data.success) {
successToast(data.message);
} else {
errorToast(data.message);
}
});
}
</script> </script>

View File

@@ -3,6 +3,7 @@
@inject ITranslationHelper translator @inject ITranslationHelper translator
@{ @{
var userLanguage = config.GetServerLanguage(); var userLanguage = config.GetServerLanguage();
var openRegistrationEnabled = config.GetServerOpenRegistration();
} }
@{ @{
ViewData["Title"] = "Register"; ViewData["Title"] = "Register";
@@ -16,10 +17,19 @@
<img src="@config.GetLogoUrl()" class="lubelogger-logo" /> <img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<div class="form-group"> <div class="form-group">
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label> <label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
<input type="text" id="inputToken" class="form-control"> @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="sendRegistrationToken()"><i class="bi bi-send"></i></button>
</div>
</div>
} else {
<input type="text" id="inputToken" class="form-control">
}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="inputUserName">@translator.Translate(userLanguage, "Email Address")</label> <label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
<input type="text" id="inputEmail" class="form-control"> <input type="text" id="inputEmail" class="form-control">
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -29,7 +39,7 @@
<div class="form-group"> <div class="form-group">
<label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label> <label for="inputUserPassword">@translator.Translate(userLanguage, "Password")</label>
<div class="input-group"> <div class="input-group">
<input type="password" id="inputUserPassword" class="form-control"> <input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performRegistration)">
<div class="input-group-text"> <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> <button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
</div> </div>

View File

@@ -25,7 +25,7 @@
<div class="form-group"> <div class="form-group">
<label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label> <label for="inputUserPassword">@translator.Translate(userLanguage, "New Password")</label>
<div class="input-group"> <div class="input-group">
<input type="password" id="inputUserPassword" class="form-control"> <input type="password" id="inputUserPassword" class="form-control" onkeyup="callBackOnEnter(event, performPasswordReset)">
<div class="input-group-text"> <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> <button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
</div> </div>

View File

@@ -34,6 +34,7 @@
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>@ViewData["Title"] - LubeLogger</title> <title>@ViewData["Title"] - LubeLogger</title>
<link rel="icon" type="image/x-icon" href="~/favicon.ico">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <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/dist/css/bootstrap-icons.css" />
<link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" /> <link rel="stylesheet" href="~/lib/bootstrap-datepicker/css/bootstrap-datepicker.min.css" />

View File

@@ -158,6 +158,8 @@
<div class="modal-content" id="inputSuppliesModalContent"></div> <div class="modal-content" id="inputSuppliesModalContent"></div>
</div> </div>
</div> </div>
<div class="stickerPrintContainer hideOnPrint">
</div>
<script> <script>
function GetVehicleId() { function GetVehicleId() {
return { return {
@@ -173,4 +175,5 @@
return { tab: "@userConfig.DefaultTab" }; return { tab: "@userConfig.DefaultTab" };
} }
bindWindowResize(); bindWindowResize();
checkRecurringTaxes();
</script> </script>

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

@@ -21,30 +21,7 @@
<div class="alert alert-danger" role="alert"> <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.") @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> </div>
@if (Model == ImportMode.GasRecord) <a class="btn btn-link" href="@($"/Vehicle/GenerateCsvSample?mode={Model.ToString()}")" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
{
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
}
else if (Model == ImportMode.ServiceRecord || Model == ImportMode.RepairRecord || Model == ImportMode.UpgradeRecord)
{
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
}
else if (Model == ImportMode.TaxRecord)
{
<a class="btn btn-link" href="/defaults/taxrecordsample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
}
else if (Model == ImportMode.SupplyRecord)
{
<a class="btn btn-link" href="/defaults/supplysample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
}
else if (Model == ImportMode.PlanRecord)
{
<a class="btn btn-link" href="/defaults/plansample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
}
else if (Model == ImportMode.OdometerRecord)
{
<a class="btn btn-link" href="/defaults/odometersample.csv" target="_blank">@translator.Translate(userLanguage, "Download Sample")</a>
}
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">

View File

@@ -54,31 +54,37 @@
</li> </li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <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"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
@@ -87,7 +93,7 @@
@foreach (string extraFieldColumn in extraFields) @foreach (string extraFieldColumn in extraFields)
{ {
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('RepairRecord')">
<div class="list-group-item"> <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"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
@@ -118,6 +124,7 @@
<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-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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -133,6 +140,7 @@
<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-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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -183,6 +191,10 @@
<li><hr class="dropdown-divider"></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="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="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><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><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><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>

View File

@@ -49,10 +49,10 @@
<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> <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> }
} </tr>
}
</tbody> </tbody>
<tfoot> <tfoot>
<tr class="d-flex fw-bold"> <tr class="d-flex fw-bold">
@@ -65,9 +65,11 @@
var yearDataToDisplay = Model.CostData.Where(x => x.Year == year); var yearDataToDisplay = Model.CostData.Where(x => x.Year == year);
if (yearDataToDisplay != null && yearDataToDisplay != default) if (yearDataToDisplay != null && yearDataToDisplay != default)
{ {
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" report-data="cost">@(StaticHelper.HideZeroCost(yearDataToDisplay.Sum(x => x.Cost).ToString("C2"), hideZero))</td> var distanceTraveled = yearDataToDisplay.Sum(x => x.DistanceTraveled);
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="distance">@(yearDataToDisplay.Sum(x => x.DistanceTraveled) != default ? $"{yearDataToDisplay.Sum(x => x.DistanceTraveled).ToString("N0")} {Model.DistanceUnit}" : "---")</td> var costAccrued = yearDataToDisplay.Sum(x => x.Cost);
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate d-none" report-data="costperdistance">@(StaticHelper.HideZeroCost(yearDataToDisplay.Sum(x => x.CostPerDistanceTraveled).ToString("C2"), hideZero, $"/{Model.DistanceUnit}"))</td> <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 else
{ {

View File

@@ -21,8 +21,8 @@
<thead class="sticky-top"> <thead class="sticky-top">
<tr class="d-flex"> <tr class="d-flex">
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Type")</th> <th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Type")</th>
<th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Cost Per Day")</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" class="col-3 flex-grow-1">@translator.Translate(userLanguage, Model.DistanceUnit)</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> <th scope="col" class="col-3 flex-grow-1">@translator.Translate(userLanguage, "Total")</th>
</tr> </tr>
</thead> </thead>

View File

@@ -67,6 +67,7 @@
<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="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-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" 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> <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>
@@ -103,49 +104,55 @@
</li> </li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_DateRefueled">@translator.Translate(userLanguage, "Date Refueled")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Delta">@translator.Translate(userLanguage, "Delta")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Consumption">@translator.Translate(userLanguage, "Consumption")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_FuelEconomy">@translator.Translate(userLanguage, "Fuel Economy")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_UnitCost">@translator.Translate(userLanguage, "Unit Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <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"> <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"> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
@@ -154,7 +161,7 @@
@foreach (string extraFieldColumn in extraFields) @foreach (string extraFieldColumn in extraFields)
{ {
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('GasRecord')">
<div class="list-group-item"> <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"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
@@ -187,6 +194,7 @@
<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-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="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" 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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -200,11 +208,12 @@
<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)'> <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 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-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">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</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-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-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="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" 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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -251,6 +260,10 @@
<li><hr class="context-menu-multiple dropdown-divider"></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="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="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><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><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> <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>
@@ -268,5 +281,12 @@
{ {
@:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false); @:convertGasConsumptionUnits(decodeHTMLEntities('@consumptionUnit'), decodeHTMLEntities('@preferredGasUnit'), false);
} }
function getGasModelData(){
return {
distanceUnit: decodeHTMLEntities('@distanceUnit'),
consumptionUnit: decodeHTMLEntities('@consumptionUnit'),
gasCostFormat: decodeHTMLEntities('@gasCostFormat'),
gasConsumptionFormat: decodeHTMLEntities('@gasConsumptionFormat')
}
}
</script> </script>

View File

@@ -12,6 +12,7 @@
var useKwh = Model.UseKwh; var useKwh = Model.UseKwh;
var useHours = Model.UseHours; var useHours = Model.UseHours;
var isNew = Model.GasRecord.Id == 0; var isNew = Model.GasRecord.Id == 0;
var useUnitFuelCost = userConfig.UseUnitForFuelCost;
string consumptionUnit; string consumptionUnit;
string distanceUnit; string distanceUnit;
if (useKwh) if (useKwh)
@@ -80,8 +81,8 @@
<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)"> <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"> <div class="input-group-text">
<select class="form-select form-select-sm" id="gasCostType"> <select class="form-select form-select-sm" id="gasCostType">
<option value="total">@translator.Translate(userLanguage,"Total")</option> <!option @(useUnitFuelCost ? "" : "selected") value="total">@translator.Translate(userLanguage,"Total")</!option>
<option value="unit">@translator.Translate(userLanguage,"Unit")</option> <!option @(useUnitFuelCost ? "selected" : "") value="unit">@translator.Translate(userLanguage,"Unit")</!option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -109,5 +109,8 @@
<li><hr class="dropdown-divider"></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="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><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> <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> </ul>

View File

@@ -54,31 +54,37 @@
</li> </li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_InitialOdometer">@translator.Translate(userLanguage, "Initial Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Distance">@translator.Translate(userLanguage, "Distance")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <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"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
@@ -87,7 +93,7 @@
@foreach (string extraFieldColumn in extraFields) @foreach (string extraFieldColumn in extraFields)
{ {
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('OdometerRecord')">
<div class="list-group-item"> <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"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
@@ -118,6 +124,7 @@
<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="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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -131,8 +138,9 @@
<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)'> <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-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="initialodometer">@odometerRecord.InitialMileage</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@odometerRecord.Mileage</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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -181,6 +189,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></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="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><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><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><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> <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>

View File

@@ -127,5 +127,8 @@
<li><hr class="context-menu-move move-header dropdown-divider"></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" 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><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> <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> </ul>

View File

@@ -82,9 +82,9 @@
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 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> <!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
</select> </select>
<label for="reminderRecurringMonth">@translator.Translate(userLanguage, "Month")</label> <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")> <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}" : $"{translator.Translate(userLanguage, "Other")}") </!option> <!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="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="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="SixMonths" @(Model.ReminderMonthInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage,"6 Months")</!option>
@@ -136,6 +136,7 @@
<script> <script>
var customMileageInterval = @Model.CustomMileageInterval; var customMileageInterval = @Model.CustomMileageInterval;
var customMonthInterval = @Model.CustomMonthInterval; var customMonthInterval = @Model.CustomMonthInterval;
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
function getReminderRecordModelData() { function getReminderRecordModelData() {
return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')} return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')}
} }

View File

@@ -140,7 +140,7 @@
<span class="badge text-bg-success">@translator.Translate(userLanguage, "Not Urgent")</span> <span class="badge text-bg-success">@translator.Translate(userLanguage, "Not Urgent")</span>
} }
</td> </td>
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1" data-column="metric"> <td class="col-2 text-truncate flex-grow-1 flex-shrink-1" data-column="metric" data-record-type="cost">
@if (reminderRecord.Metric == ReminderMetric.Date) @if (reminderRecord.Metric == ReminderMetric.Date)
{ {
@reminderRecord.Date.ToShortDateString() @reminderRecord.Date.ToShortDateString()
@@ -200,6 +200,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></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="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><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> <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> </ul>

View File

@@ -168,7 +168,6 @@
</div> </div>
</div> </div>
} }
<div id="vehicleHistoryReport" class="showOnPrint"></div>
<script> <script>
getSelectedMetrics(); getSelectedMetrics();

View File

@@ -6,16 +6,17 @@
var userLanguage = userConfig.UserLanguage; var userLanguage = userConfig.UserLanguage;
} }
@model ReportParameter @model ReportParameter
<div id="columnSelector"> <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"> <ul class="list-group">
@foreach(string column in Model.VisibleColumns) @foreach (string column in Model.VisibleColumns)
{ {
<li class="list-group-item text-start"> <li class="list-group-item text-start">
<input class="form-check-input column-default" type="checkbox" value="@column" id="visibleColumn_@column" checked> <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> <label class="form-check-label stretched-link" for="visibleColumn_@column">@(translator.Translate(userLanguage, column == nameof(GenericReportModel.DataType) ? "Type" : column))</label>
</li> </li>
} }
@foreach(string extraField in Model.ExtraFields) @foreach (string extraField in Model.ExtraFields)
{ {
<li class="list-group-item text-start"> <li class="list-group-item text-start">
<input class="form-check-input column-extrafield" type="checkbox" value="@extraField" id="extraField_@extraField"> <input class="form-check-input column-extrafield" type="checkbox" value="@extraField" id="extraField_@extraField">
@@ -23,4 +24,40 @@
</li> </li>
} }
</ul> </ul>
</div> </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

@@ -54,31 +54,37 @@
</li> </li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <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"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
@@ -87,7 +93,7 @@
@foreach (string extraFieldColumn in extraFields) @foreach (string extraFieldColumn in extraFields)
{ {
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('ServiceRecord')">
<div class="list-group-item"> <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"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
@@ -118,6 +124,7 @@
<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-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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -133,6 +140,7 @@
<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-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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -181,6 +189,10 @@
<li><hr class="dropdown-divider"></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="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="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><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><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><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>

View File

@@ -0,0 +1,277 @@
@using CarCareTracker.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="lubelogger-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="lubelogger-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="lubelogger-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

@@ -54,43 +54,49 @@
</li> </li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_PartNumber">@translator.Translate(userLanguage, "Part Number")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Supplier">@translator.Translate(userLanguage, "Supplier")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Quantity">@translator.Translate(userLanguage, "Quantity")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <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"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
@@ -99,7 +105,7 @@
@foreach (string extraFieldColumn in extraFields) @foreach (string extraFieldColumn in extraFields)
{ {
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('SupplyRecord')">
<div class="list-group-item"> <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"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
@@ -132,6 +138,7 @@
<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-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="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" 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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -149,6 +156,7 @@
<td class="col-2 flex-grow-1 col-xl-3 text-truncate" data-column="description">@supplyRecord.Description</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="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" 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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -194,6 +202,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></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="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><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> <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> </ul>
@if (userColumnPreferences.Any()) @if (userColumnPreferences.Any())

View File

@@ -57,9 +57,9 @@
<input class="form-check-input" type="checkbox" onChange="enableTaxRecurring()" role="switch" id="taxIsRecurring" checked="@Model.IsRecurring"> <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> <label class="form-check-label" for="taxIsRecurring">@translator.Translate(userLanguage,"Is Recurring")</label>
</div> </div>
<label for="taxRecurringMonth">@translator.Translate(userLanguage,"Month")</label> <label for="taxRecurringMonth">@translator.Translate(userLanguage,"Time")</label>
<select class="form-select" onchange="checkCustomMonthIntervalForTax()" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")> <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}" : $"{translator.Translate(userLanguage, "Other")}") </!option> <!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="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="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="SixMonths" @(Model.RecurringInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>@translator.Translate(userLanguage, "6 Months")</!option>
@@ -116,6 +116,7 @@
<script> <script>
var uploadedFiles = []; var uploadedFiles = [];
var customMonthInterval = @Model.CustomMonthInterval; var customMonthInterval = @Model.CustomMonthInterval;
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
var recurringReminderRecordId = []; var recurringReminderRecordId = [];
getUploadedFilesFromModel(); getUploadedFilesFromModel();
function getUploadedFilesFromModel() { function getUploadedFilesFromModel() {

View File

@@ -54,25 +54,31 @@
</li> </li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <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"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
@@ -81,7 +87,7 @@
@foreach (string extraFieldColumn in extraFields) @foreach (string extraFieldColumn in extraFields)
{ {
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('TaxRecord')">
<div class="list-group-item"> <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"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
@@ -111,6 +117,7 @@
<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-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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -125,6 +132,7 @@
<td class="col-3 flex-grow-1 col-xl-1 text-truncate" data-column="date">@taxRecord.Date.ToShortDateString()</td> <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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -170,6 +178,9 @@
<li><hr class="context-menu-multiple dropdown-divider"></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="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><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> <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> </ul>
@if (userColumnPreferences.Any()) @if (userColumnPreferences.Any())

View File

@@ -54,31 +54,37 @@
</li> </li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li> <li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Visible Columns")</h6></li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Description">@translator.Translate(userLanguage, "Description")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Cost">@translator.Translate(userLanguage, "Cost")</label>
</div> </div>
</li> </li>
<li class="dropdown-item"> <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"> <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> <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> <label class="form-check-label stretched-link" for="chkCol_Notes">@translator.Translate(userLanguage, "Notes")</label>
@@ -87,7 +93,7 @@
@foreach (string extraFieldColumn in extraFields) @foreach (string extraFieldColumn in extraFields)
{ {
var elementId = Guid.NewGuid(); var elementId = Guid.NewGuid();
<li class="dropdown-item"> <li class="dropdown-item" draggable="true" ondragstart="handleTableColumnDragStart(event)" ondragover="handleTableColumnDragOver(event)" ondragend="handleTableColumnDragEnd('UpgradeRecord')">
<div class="list-group-item"> <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"> <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> <label class="form-check-label stretched-link" for="@elementId">@extraFieldColumn</label>
@@ -118,6 +124,7 @@
<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-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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -133,6 +140,7 @@
<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-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-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-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> <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) @foreach (string extraFieldColumn in extraFields)
{ {
@@ -182,6 +190,10 @@
<li><hr class="dropdown-divider"></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="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="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><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><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><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>

View File

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

View File

@@ -8,11 +8,22 @@
var userLanguage = userConfig.UserLanguage; var userLanguage = userConfig.UserLanguage;
var extraFields = Model.ReportParameters.ExtraFields; var extraFields = Model.ReportParameters.ExtraFields;
} }
<div class="vehicleDetailTabContainer"> <div style="page-break-after: always;">
<div class="row mt-2"> <div class="row mt-2">
<div class="d-flex"> <div class="d-flex">
<img src="@config.GetLogoUrl()" class="lubelogger-logo" /> <img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<span class="display-6 ms-5">@translator.Translate(userLanguage, "Vehicle Maintenance Report")</span> <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>
</div> </div>
<hr /> <hr />
@@ -81,26 +92,26 @@
<hr /> <hr />
@if (Model.TotalDepreciation != default) @if (Model.TotalDepreciation != default)
{ {
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
@(Model.TotalDepreciation > 0 ? translator.Translate(userLanguage, "Depreciation") : translator.Translate(userLanguage, "Appreciation")) @(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> </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 /> <hr />
} }
<div class="row"> <div class="row">
@@ -165,3 +176,113 @@
</div> </div>
</div> </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="lubelogger-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

@@ -15,18 +15,19 @@
"DisableRegistration": false, "DisableRegistration": false,
"EnableRootUserOIDC": false, "EnableRootUserOIDC": false,
"HideZero": false, "HideZero": false,
"AutomaticDecimalFormat": false, "AutomaticDecimalFormat": false,
"EnableAutoReminderRefresh": false, "EnableAutoReminderRefresh": false,
"EnableAutoOdometerInsert": false, "EnableAutoOdometerInsert": false,
"EnableShopSupplies": false, "EnableShopSupplies": false,
"EnableExtraFieldColumns": false, "EnableExtraFieldColumns": false,
"UseUKMPG": false, "UseUKMPG": false,
"UseThreeDecimalGasCost": true, "UseThreeDecimalGasCost": true,
"UseThreeDecimalGasConsumption": true, "UseThreeDecimalGasConsumption": true,
"UseMarkDownOnSavedNotes": false, "UseMarkDownOnSavedNotes": false,
"HideSoldVehicles": false, "HideSoldVehicles": false,
"PreferredGasMileageUnit": "", "PreferredGasMileageUnit": "",
"UserColumnPreferences": [], "UserColumnPreferences": [],
"UseUnitForFuelCost": false,
"PreferredGasUnit": "", "PreferredGasUnit": "",
"UserLanguage": "en_US", "UserLanguage": "en_US",
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ], "VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],

View File

@@ -8,13 +8,7 @@ services:
restart: unless-stopped restart: unless-stopped
# volumes used to keep data persistent # volumes used to keep data persistent
volumes: volumes:
- config:/App/config
- data:/App/data - data:/App/data
- translations:/App/wwwroot/translations
- documents:/App/wwwroot/documents
- images:/App/wwwroot/images
- temp:/App/wwwroot/temp
- log:/App/log
- keys:/root/.aspnet/DataProtection-Keys - keys:/root/.aspnet/DataProtection-Keys
# expose port and/or use serving via traefik # expose port and/or use serving via traefik
ports: ports:
@@ -35,12 +29,6 @@ services:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
volumes: volumes:
config:
data: data:
translations:
documents:
images:
temp:
log:
keys: keys:
postgres: postgres:

View File

@@ -8,13 +8,7 @@ services:
restart: unless-stopped restart: unless-stopped
# volumes used to keep data persistent # volumes used to keep data persistent
volumes: volumes:
- config:/App/config
- data:/App/data - data:/App/data
- translations:/App/wwwroot/translations
- documents:/App/wwwroot/documents
- images:/App/wwwroot/images
- temp:/App/wwwroot/temp
- log:/App/log
- keys:/root/.aspnet/DataProtection-Keys - keys:/root/.aspnet/DataProtection-Keys
# expose port and/or use serving via traefik # expose port and/or use serving via traefik
ports: ports:
@@ -40,13 +34,7 @@ services:
traefik.http.services.whoami.loadbalancer.server.port: 5000 traefik.http.services.whoami.loadbalancer.server.port: 5000
volumes: volumes:
config:
data: data:
translations:
documents:
images:
temp:
log:
keys: keys:
networks: networks:

View File

@@ -8,13 +8,7 @@ services:
restart: unless-stopped restart: unless-stopped
# volumes used to keep data persistent # volumes used to keep data persistent
volumes: volumes:
- config:/App/config
- data:/App/data - data:/App/data
- translations:/App/wwwroot/translations
- documents:/App/wwwroot/documents
- images:/App/wwwroot/images
- temp:/App/wwwroot/temp
- log:/App/log
- keys:/root/.aspnet/DataProtection-Keys - keys:/root/.aspnet/DataProtection-Keys
# expose port and/or use serving via traefik # expose port and/or use serving via traefik
ports: ports:
@@ -23,11 +17,5 @@ services:
- .env - .env
volumes: volumes:
config:
data: data:
translations:
documents:
images:
temp:
log:
keys: keys:

View File

@@ -2,7 +2,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="../favicon.ico">
<title>LubeLogger Configurator</title> <title>LubeLogger Configurator</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
@@ -49,9 +49,13 @@
<input type="text" id="inputFileExtensions" class="form-control"> <input type="text" id="inputFileExtensions" class="form-control">
<small class="text-body-secondary">Blank for default, * for all files</small> <small class="text-body-secondary">Blank for default, * for all files</small>
</div> </div>
<div class="form-check form-switch">
<input class="form-check-input"type="checkbox" role="switch" id="inputCustomWidgets">
<label class="form-check-label" for="inputCustomWidgets">Custom Widgets</label>
</div>
</div> </div>
<div class="col-6"> <div class="col-6">
<div class="form-group"> <div class="form-group">
<label for="inputCustomLogo">Custom Logo URL</label> <label for="inputCustomLogo">Custom Logo URL</label>
<input type="text" id="inputCustomLogo" class="form-control"> <input type="text" id="inputCustomLogo" class="form-control">
<small class="text-body-secondary">Default size: 204x48</small> <small class="text-body-secondary">Default size: 204x48</small>
@@ -66,6 +70,10 @@
<input type="text" id="inputWebHook" class="form-control"> <input type="text" id="inputWebHook" class="form-control">
<small class="text-body-secondary">URL to WebHook Consumer</small> <small class="text-body-secondary">URL to WebHook Consumer</small>
</div> </div>
<div class="form-check form-switch">
<input class="form-check-input"type="checkbox" role="switch" id="inputInvariantAPI">
<label class="form-check-label" for="inputInvariantAPI">Invariant API</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -219,7 +227,9 @@
<textarea id="outputModalText" readonly style="width:100%; height:450px;"></textarea> <textarea id="outputModalText" readonly style="width:100%; height:450px;"></textarea>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary btn-strip me-auto" onclick="removeDoubleQuotes()">Remove Double Quotes</button> <button type="button" class="btn btn-secondary btn-strip me-auto" onclick="removeDoubleQuotes()">Remove Double Quotes</button>
<input id="appSettingsUpload" onChange="readUploadedFile()" class="d-none" type="file" accept="application/json">
<button type="button" class="btn btn-secondary btn-upload me-auto" onclick="uploadAndMerge()">Upload appsettings.json</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary btn-copy" onclick="copyToClipboard()">Copy</button> <button type="button" class="btn btn-primary btn-copy" onclick="copyToClipboard()">Copy</button>
</div> </div>
@@ -229,6 +239,44 @@
</div> </div>
</body> </body>
<script> <script>
function uploadAndMerge(){
$("#appSettingsUpload").click();
}
function readUploadedFile(){
let fl_files = $("#appSettingsUpload")[0].files; // JS FileList object
if (fl_files.length == 0) {
return;
}
// use the 1st file from the list
let fl_file = fl_files[0];
let reader = new FileReader(); // built in API
let display_file = ( e ) => { // set the contents of the <textarea>
mergeIntoUploadedFile(e.target.result);
};
let on_reader_load = ( fl ) => {
return display_file; // a function
};
// Closure to capture the file information.
reader.onload = on_reader_load( fl_file );
// Read the file as text.
reader.readAsText( fl_file );
}
function mergeIntoUploadedFile(fileContents){
var newJsonObject = JSON.parse("{" + $("#outputModalText").text() + "}");
var currentJsonObject = JSON.parse(fileContents);
var mergedJsonObject = {...currentJsonObject, ...newJsonObject};
$("#outputModalLabel").text("Content for appsettings.json");
$("#outputModalText").text(JSON.stringify(mergedJsonObject, null, 2));
//clear out uploaded file content
$("#appSettingsUpload").val("");
}
function removeDoubleQuotes(){ function removeDoubleQuotes(){
var currentText = $("#outputModalText").text(); var currentText = $("#outputModalText").text();
$("#outputModalText").text(currentText.replaceAll('"', '')); $("#outputModalText").text(currentText.replaceAll('"', ''));
@@ -262,6 +310,12 @@ function generateConfig(){
if ($("#inputPostgres").val().trim() != ''){ if ($("#inputPostgres").val().trim() != ''){
windowConfig["POSTGRES_CONNECTION"]=$("#inputPostgres").val(); windowConfig["POSTGRES_CONNECTION"]=$("#inputPostgres").val();
} }
if ($("#inputCustomWidgets").is(":checked")){
windowConfig["LUBELOGGER_CUSTOM_WIDGETS"]=$('#inputCustomWidgets').is(':checked');
}
if ($("#inputInvariantAPI").is(":checked")){
windowConfig["LUBELOGGER_INVARIANT_API"]=$('#inputInvariantAPI').is(':checked');
}
if ($('#inputSmtpServer').val().trim() != ''){ if ($('#inputSmtpServer').val().trim() != ''){
windowConfig["MailConfig"] = { windowConfig["MailConfig"] = {
EmailServer: $("#inputSmtpServer").val(), EmailServer: $("#inputSmtpServer").val(),
@@ -305,6 +359,11 @@ function generateConfig(){
$("#outputModalLabel").text("Append into appsettings.json"); $("#outputModalLabel").text("Append into appsettings.json");
$("#outputModalText").text(JSON.stringify(windowConfig, null, 2).slice(1,-1)); $("#outputModalText").text(JSON.stringify(windowConfig, null, 2).slice(1,-1));
$(".btn-strip").hide(); $(".btn-strip").hide();
if (jQuery.isEmptyObject(windowConfig)){
$(".btn-upload").hide();
} else {
$(".btn-upload").show();
}
$("#outputModal").modal("show"); $("#outputModal").modal("show");
} else { } else {
var dockerConfig = []; var dockerConfig = [];
@@ -327,6 +386,12 @@ function generateConfig(){
if ($("#inputPostgres").val().trim() != ''){ if ($("#inputPostgres").val().trim() != ''){
dockerConfig.push(`POSTGRES_CONNECTION="${$('#inputPostgres').val()}"`); dockerConfig.push(`POSTGRES_CONNECTION="${$('#inputPostgres').val()}"`);
} }
if ($("#inputCustomWidgets").is(":checked")){
dockerConfig.push(`LUBELOGGER_CUSTOM_WIDGETS="${$('#inputCustomWidgets').is(':checked')}"`);
}
if ($("#inputInvariantAPI").is(":checked")){
dockerConfig.push(`LUBELOGGER_INVARIANT_API="${$('#inputInvariantAPI').is(':checked')}"`);
}
if ($('#inputSmtpServer').val().trim() != ''){ if ($('#inputSmtpServer').val().trim() != ''){
dockerConfig.push(`MailConfig__EmailServer="${$('#inputSmtpServer').val()}"`); dockerConfig.push(`MailConfig__EmailServer="${$('#inputSmtpServer').val()}"`);
dockerConfig.push(`MailConfig__EmailFrom="${$('#inputSmtpFrom').val()}"`); dockerConfig.push(`MailConfig__EmailFrom="${$('#inputSmtpFrom').val()}"`);
@@ -355,6 +420,7 @@ function generateConfig(){
$("#outputModalLabel").text("Content for .env"); $("#outputModalLabel").text("Content for .env");
$("#outputModalText").text(dockerConfig.join("\r\n")); $("#outputModalText").text(dockerConfig.join("\r\n"));
$(".btn-strip").show(); $(".btn-strip").show();
$(".btn-upload").hide();
$("#outputModal").modal("show"); $("#outputModal").modal("show");
} }
} }

View File

@@ -81,6 +81,34 @@ html {
color: #000 !important; color: #000 !important;
overflow: visible; overflow: visible;
} }
.stickerPrintContainer {
background-color: #fff !important;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
margin: 0;
font-size: 14px;
line-height: 18px;
color: #000 !important;
overflow: visible;
z-index: 1030;
}
.reminderSticker {
width: 98%;
aspect-ratio: 1/1;
border-style: dashed;
border-width: 2px;
page-break-after: always;
}
.recordSticker {
height: 100%;
page-break-after: always;
}
.stickerNote {
height:100%;
}
body { body {
background-color: #fff !important; background-color: #fff !important;
@@ -291,6 +319,11 @@ html {
position: absolute; position: absolute;
} }
.table-context-menu > li > .dropdown-item:hover {
background-color: rgba(var(--bs-primary-rgb)) !important;
color: #fff !important;
}
html[data-bs-theme="dark"] .table-context-menu { html[data-bs-theme="dark"] .table-context-menu {
background-color: rgba(33, 37, 41, 0.7); background-color: rgba(33, 37, 41, 0.7);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
@@ -473,6 +506,12 @@ html[data-bs-theme="light"] .api-method:hover {
object-fit: scale-down; object-fit: scale-down;
pointer-events: none; pointer-events: none;
} }
.lubelogger-logo-sticker {
height: 6rem;
width: auto;
object-fit: scale-down;
pointer-events: none;
}
::-ms-reveal { ::-ms-reveal {
display: none; display: none;
@@ -481,4 +520,12 @@ html[data-bs-theme="light"] .api-method:hover {
.lubelogger-report-banner { .lubelogger-report-banner {
border-top: thin solid black; border-top: thin solid black;
text-align: center; text-align: center;
}
.attachment-badge-xs {
padding-left: 0.25em;
padding-right: 0.25em;
font-size: 0.6em;
font-weight: 500;
top: 15%
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
Date,Odometer,FuelConsumed,Cost,IsFillToFull,MissedFuelUp,Notes
5/8/2020,204836,8.331,16.24,True,False,
5/30/2020,205056,11.913,25.72,True,False,
1 Date Odometer FuelConsumed Cost IsFillToFull MissedFuelUp Notes
2 5/8/2020 204836 8.331 16.24 True False
3 5/30/2020 205056 11.913 25.72 True False

View File

@@ -1,2 +0,0 @@
Date,Odometer,Notes
1/1/2024,260001,test test
1 Date Odometer Notes
2 1/1/2024 260001 test test

View File

@@ -1,2 +0,0 @@
DateCreated,DateModified,Description,Notes,Type,Priority,Progress,Cost
1/19/2024 6:01:02 PM,1/19/2024 7:32:58 PM,Repair Exhaust,,RepairRecord,Normal,Testing,$50.00
1 DateCreated DateModified Description Notes Type Priority Progress Cost
2 1/19/2024 6:01:02 PM 1/19/2024 7:32:58 PM Repair Exhaust RepairRecord Normal Testing $50.00

View File

@@ -33,15 +33,7 @@ td, th {
<h2>{VehicleInformation}</h2> <h2>{VehicleInformation}</h2>
<table> <table>
<tr> <tr>
<th> {TableHeader}
Urgency
</th>
<th>
Description
</th>
<th>
Due
</th>
</tr> </tr>
{TableBody} {TableBody}
</table> </table>

View File

@@ -1,3 +0,0 @@
Date,Odometer,Description,Notes,Cost
01/01/2020,45000,Test Description 1,Test Note 1,20.60
01/02/2020,47000,Test Description 2,Test Note 2,40.45
1 Date Odometer Description Notes Cost
2 01/01/2020 45000 Test Description 1 Test Note 1 20.60
3 01/02/2020 47000 Test Description 2 Test Note 2 40.45

View File

@@ -1,2 +0,0 @@
Date,PartNumber,PartSupplier,PartQuantity,Description,Cost,Notes
1/16/2024,EVA17872045551,Evan Fischer,1,Front Bumper,$95.14,Altima Activities
1 Date PartNumber PartSupplier PartQuantity Description Cost Notes
2 1/16/2024 ‎EVA17872045551 Evan Fischer 1 Front Bumper $95.14 Altima Activities

View File

@@ -1,3 +0,0 @@
Date,Description,Notes,Cost
01/01/2020,Test Description 1,Test Note 1,20.60
01/02/2020,Test Description 2,Test Note 2,40.45
1 Date Description Notes Cost
2 01/01/2020 Test Description 1 Test Note 1 20.60
3 01/02/2020 Test Description 2 Test Note 2 40.45

View File

@@ -183,7 +183,7 @@ function hidePinnedNotes(vehicleId) {
} }
} }
function filterGarage(sender, isSort) { function filterGarage(sender) {
var rowData = $(".garage-item"); var rowData = $(".garage-item");
if (sender == undefined) { if (sender == undefined) {
rowData.removeClass('override-hide'); rowData.removeClass('override-hide');
@@ -191,14 +191,9 @@ function filterGarage(sender, isSort) {
} }
var tagName = sender.textContent; var tagName = sender.textContent;
if ($(sender).hasClass("bg-primary")) { if ($(sender).hasClass("bg-primary")) {
if (!isSort) { rowData.removeClass('override-hide');
rowData.removeClass('override-hide'); $(sender).removeClass('bg-primary');
$(sender).removeClass('bg-primary'); $(sender).addClass('bg-secondary');
$(sender).addClass('bg-secondary');
} else {
rowData.addClass('override-hide');
$(`[data-tags~='${tagName}']`).removeClass('override-hide');
}
} else { } else {
//hide table rows. //hide table rows.
rowData.addClass('override-hide'); rowData.addClass('override-hide');
@@ -264,14 +259,13 @@ function sortGarage(sender, isMobile) {
//restore table //restore table
sender.removeClass('sort-desc'); sender.removeClass('sort-desc');
sender.html(isMobile ? `<span class="ms-2 display-3">${garageIcon}${sortColumn}</span>` : `${garageIcon}${sortColumn}`); sender.html(isMobile ? `<span class="ms-2 display-3">${garageIcon}${sortColumn}</span>` : `${garageIcon}${sortColumn}`);
$('.vehiclesContainer').html(storedTableRowState); resetSortGarage();
filterGarage($(".tagfilter.bg-primary").get(0), true);
} else { } else {
//first time sorting. //first time sorting.
//check if table was sorted before by a different column(only relevant to fuel tab) //check if table was sorted before by a different column(only relevant to fuel tab)
if (storedTableRowState != null && ($(".sort-asc").length > 0 || $(".sort-desc").length > 0)) { if ($("[default-sort]").length > 0 && ($(".sort-asc").length > 0 || $(".sort-desc").length > 0)) {
//restore table state. //restore table state.
$('.vehiclesContainer').html(storedTableRowState); resetSortGarage();
//reset other sorted columns //reset other sorted columns
if ($(".sort-asc").length > 0) { if ($(".sort-asc").length > 0) {
$(".sort-asc").html($(".sort-asc").html().replace(sortAscIcon, "")); $(".sort-asc").html($(".sort-asc").html().replace(sortAscIcon, ""));
@@ -284,12 +278,28 @@ function sortGarage(sender, isMobile) {
} }
sender.addClass('sort-asc'); sender.addClass('sort-asc');
sender.html(isMobile ? `<span class="ms-2 display-3">${garageIcon}${sortColumn}${sortAscIcon}</span>` : `${garageIcon}${sortColumn}${sortAscIcon}`); sender.html(isMobile ? `<span class="ms-2 display-3">${garageIcon}${sortColumn}${sortAscIcon}</span>` : `${garageIcon}${sortColumn}${sortAscIcon}`);
storedTableRowState = null; //append sortRowId to the vehicle container
storedTableRowState = $('.vehiclesContainer').html(); if ($("[default-sort]").length == 0) {
$(`.garage-item`).map((index, elem) => {
$(elem).attr("default-sort", index);
});
}
sortVehicles(false); sortVehicles(false);
} }
} }
} }
function resetSortGarage() {
var rowData = $(`.garage-item`);
var sortedRow = rowData.toArray().sort((a, b) => {
var currentVal = $(a).attr('default-sort');
var nextVal = $(b).attr('default-sort');
return currentVal - nextVal;
});
$(".garage-item-add").map((index, elem) => {
sortedRow.push(elem);
})
$(`.vehiclesContainer`).html(sortedRow);
}
let dragged = null; let dragged = null;
let draggedId = 0; let draggedId = 0;

View File

@@ -314,17 +314,21 @@ function updateMPGLabels() {
var minLabel = $("#minFuelMileageLabel"); var minLabel = $("#minFuelMileageLabel");
var maxLabel = $("#maxFuelMileageLabel"); var maxLabel = $("#maxFuelMileageLabel");
var totalConsumedLabel = $("#totalFuelConsumedLabel"); var totalConsumedLabel = $("#totalFuelConsumedLabel");
if (averageLabel.length > 0 && minLabel.length > 0 && maxLabel.length > 0 && totalConsumedLabel.length > 0) { var totalDistanceLabel = $("#totalDistanceLabel");
if (averageLabel.length > 0 && minLabel.length > 0 && maxLabel.length > 0 && totalConsumedLabel.length > 0 && totalDistanceLabel.length > 0) {
var rowsToAggregate = $("[data-aggregated='true']").parent(":not('.override-hide')"); var rowsToAggregate = $("[data-aggregated='true']").parent(":not('.override-hide')");
var rowsUnaggregated = $("[data-aggregated='false']").parent(":not('.override-hide')"); var rowsUnaggregated = $("[data-aggregated='false']").parent(":not('.override-hide')");
var rowMPG = rowsToAggregate.children('[data-gas-type="fueleconomy"]').toArray().map(x => globalParseFloat(x.textContent)); var rowMPG = rowsToAggregate.children('[data-gas-type="fueleconomy"]').toArray().map(x => globalParseFloat(x.textContent));
var rowNonZeroMPG = rowMPG.filter(x => x > 0);
var maxMPG = rowMPG.length > 0 ? rowMPG.reduce((a, b) => a > b ? a : b) : 0; var maxMPG = rowMPG.length > 0 ? rowMPG.reduce((a, b) => a > b ? a : b) : 0;
var minMPG = rowMPG.length > 0 ? rowMPG.filter(x=>x>0).reduce((a, b) => a < b ? a : b) : 0; var minMPG = rowMPG.length > 0 && rowNonZeroMPG.length > 0 ? rowNonZeroMPG.reduce((a, b) => a < b ? a : b) : 0;
var totalMilesTraveled = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0; var totalMilesTraveled = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
var totalGasConsumed = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0; var totalGasConsumed = rowMPG.length > 0 ? rowsToAggregate.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
var totalUnaggregatedGasConsumed = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0; var totalUnaggregatedGasConsumed = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="consumption"]').toArray().map(x => globalParseFloat(x.textContent)).reduce((a, b) => a + b) : 0;
var totalMilesTraveledUnaggregated = rowsUnaggregated.length > 0 ? rowsUnaggregated.children('[data-gas-type="mileage"]').toArray().map(x => globalParseFloat($(x).attr("data-gas-aggregate"))).reduce((a, b) => a + b) : 0;
var fullGasConsumed = totalGasConsumed + totalUnaggregatedGasConsumed; var fullGasConsumed = totalGasConsumed + totalUnaggregatedGasConsumed;
if (totalGasConsumed > 0) { var fullDistanceTraveled = totalMilesTraveled + totalMilesTraveledUnaggregated;
if (totalGasConsumed > 0 && rowNonZeroMPG.length > 0) {
var averageMPG = totalMilesTraveled / totalGasConsumed; var averageMPG = totalMilesTraveled / totalGasConsumed;
if (!getGlobalConfig().useMPG && $("[data-gas='fueleconomy']").attr("data-unit") != 'km/l' && averageMPG > 0) { if (!getGlobalConfig().useMPG && $("[data-gas='fueleconomy']").attr("data-unit") != 'km/l' && averageMPG > 0) {
averageMPG = 100 / averageMPG; averageMPG = 100 / averageMPG;
@@ -333,6 +337,11 @@ function updateMPGLabels() {
} else { } else {
averageLabel.text(`${averageLabel.text().split(':')[0]}: ${globalFloatToString('0.00')}`); averageLabel.text(`${averageLabel.text().split(':')[0]}: ${globalFloatToString('0.00')}`);
} }
if (fullDistanceTraveled > 0) {
totalDistanceLabel.text(`${totalDistanceLabel.text().split(':')[0]}: ${fullDistanceTraveled} ${getGasModelData().distanceUnit}`);
} else {
totalDistanceLabel.text(`${totalDistanceLabel.text().split(':')[0]}: 0 ${getGasModelData().distanceUnit}`);
}
if (fullGasConsumed > 0) { if (fullGasConsumed > 0) {
totalConsumedLabel.text(`${totalConsumedLabel.text().split(':')[0]}: ${globalFloatToString(fullGasConsumed.toFixed(2))}`); totalConsumedLabel.text(`${totalConsumedLabel.text().split(':')[0]}: ${globalFloatToString(fullGasConsumed.toFixed(2))}`);
} else { } else {

View File

@@ -55,16 +55,37 @@ function performPasswordReset() {
}); });
} }
function handlePasswordKeyPress(event) {
if (event.keyCode == 13) {
performLogin();
}
}
function remoteLogin() { function remoteLogin() {
$.get('/Login/GetRemoteLoginLink', function (data) { $.get('/Login/GetRemoteLoginLink', function (data) {
if (data) { if (data) {
window.location.href = data; window.location.href = data;
} }
}) })
}
function sendRegistrationToken() {
Swal.fire({
title: 'Please Provide an Email Address',
html: `
<input type="text" id="inputTokenEmail" class="swal2-input" placeholder="Email Address" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Send',
focusConfirm: false,
preConfirm: () => {
const tokenEmail = $("#inputTokenEmail").val();
if (!tokenEmail || tokenEmail.trim() == '') {
Swal.showValidationMessage(`Please enter a valid email address`);
}
return { tokenEmail }
},
}).then(function (result) {
if (result.isConfirmed) {
$.post('/Login/SendRegistrationToken', { emailAddress: result.value.tokenEmail }, function (data) {
if (data.success) {
successToast(data.message);
} else {
errorToast(data.message);
}
});
}
});
} }

Some files were not shown because too many files have changed in this diff Show More