Compare commits

..

188 Commits

Author SHA1 Message Date
Hargata Softworks
6cf733b9c6 Merge pull request #558 from hargata/Hargata/545
enable extra fields to be imported via CSV
2024-07-09 14:52:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1eb6e2cedf enable extra fields to be imported via CSV 2024-07-09 14:48:09 -06:00
Hargata Softworks
ef4deaba8f Merge pull request #557 from hargata/Hargata/512
recurring interval are now based on reminder metric.
2024-07-09 11:16:09 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e8c196c2fa recurring interval are now based on reminder metric. 2024-07-09 11:15:02 -06:00
Hargata Softworks
f7c9db6353 Merge pull request #556 from hargata/Hargata/mailconfig.cleanup
Clean up MailConfig and update npsql
2024-07-09 10:35:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4ce720ff97 Update npgSQL 2024-07-09 10:34:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a96011629b clean up mailconfig 2024-07-09 10:32:40 -06:00
Hargata Softworks
9dcdcf97e8 Merge pull request #508 from snpaul22/main
Create app schema automatically
2024-07-06 17:40:13 -06:00
snpaul22
5148338f52 Merge branch 'hargata:main' into main 2024-07-06 18:54:14 -04:00
Hargata Softworks
0d3c04d8f8 Merge pull request #554 from hargata/Hargata/root.user.update
Make it easier to update root user credentials and patch bug.
2024-07-05 11:26:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
15328a14b4 Make it easier to update root user credentials and patch bug. 2024-07-05 11:18:46 -06:00
Hargata Softworks
2d092f722a Merge pull request #550 from hargata/Hargata/disabled.init.odo
updated button color.
2024-07-02 15:05:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8825cb9b9b updated button color. 2024-07-02 15:05:16 -06:00
Hargata Softworks
2f17e303ab Merge pull request #549 from hargata/Hargata/disabled.init.odo
Disable the initial odometer reading field
2024-07-02 11:51:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4b56c8a343 a better implementation of disabled attribute 2024-07-02 11:09:41 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7b6b62c623 Disable the initial odometer reading field if it's non-zero to prevent accidental editing. 2024-07-02 10:54:11 -06:00
Hargata Softworks
07f5e66491 Merge pull request #519 from NateWright/main
Add maskable icons for Android PWA
2024-06-18 08:25:49 -06:00
Hargata Softworks
fe633f3220 Merge pull request #542 from kapcake/patch-1
Update money regex to allow more than 6 figures on integer part
2024-06-18 08:25:00 -06:00
kapcake
af1090553f Update money regex to not allow unlimited figures
The regex now allows up to 8 groups of 3 digits on the integer part, which is within the bounds of C# decimal, preventing the user to go out of bounds on the form input.
2024-06-18 15:14:26 +02:00
kapcake
92c2e66660 Update money regex to allow more than 6 figures on integer part 2024-06-18 15:02:20 +02:00
Nathan Wright
08372f9dcb Merge branch 'hargata:main' into main 2024-06-08 15:20:31 -04:00
Hargata Softworks
64ea0e2eee Merge pull request #533 from hargata/Hargata/532
add support for smtp clients that requires no authentication.
2024-05-31 09:11:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7ab476a88f add support for smtp clients that requires no authentication. 2024-05-31 09:11:09 -06:00
Hargata Softworks
de41ca911d Merge pull request #530 from hargata/Hargata/527
Minor Bug Fixes
2024-05-29 09:36:56 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dde9688f96 Updated supplies usage validation to allow for free supplies. 2024-05-28 14:54:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c1ca63edc0 removed unnecessary tostring 2024-05-28 14:26:05 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cbc430499f added new currency formatting function 2024-05-28 14:14:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4da9fa4802 See 514 2024-05-28 13:55:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
42586d9556 Updated version number 2024-05-28 12:56:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ea4387d4ab Add missing translation keys 2024-05-28 12:53:20 -06:00
Hargata Softworks
0707b515ab Merge pull request #525 from hargata/Hargata/sponsor.tiers
Updated Sponsors File Path
2024-05-21 15:42:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78ae71fc46 Updated Sponsors File Path 2024-05-21 15:41:05 -06:00
Hargata Softworks
3f62cd40e7 Merge pull request #524 from hargata/Hargata/sponsor.tiers
Add Sponsor Section
2024-05-21 15:32:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
47657c0093 Added sponsor section. 2024-05-21 15:29:46 -06:00
Hargata Softworks
a5b0fde4b6 Merge pull request #522 from hargata/Hargata/swal.uncensored
Hargata/swal.uncensored
2024-05-20 12:26:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78cc0b34b1 Updated version number 2024-05-20 12:25:07 -06:00
Hargata Softworks
e3017e986b Merge pull request #520 from hargata/Hargata/license.change
Removed commercial license restrictions from license.
2024-05-20 12:22:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e12cd876db removed unused files 2024-05-20 12:21:54 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5292e4b814 utilize uncensored version of sweet alert 2024-05-20 12:16:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dbdd16ab89 Removed commercial license restrictions from license. 2024-05-14 08:46:47 -06:00
nwright
61c2600286 added maskable icons 2024-05-13 19:34:03 -04:00
snpaul22
163a33ae3a Create init.sql
Script checks for "app" schema in Postgres during startup. It will create the schema automatically if it doesn't exist or skip if it does exist.
2024-05-04 11:27:50 -04:00
snpaul22
37d064aa62 Update docker-compose.postgresql.yml
Added Postgres volume bind for initialization script
2024-05-04 11:23:55 -04:00
Hargata Softworks
ddc3c2e1b5 Merge pull request #503 from hargata/Hargata/null.mail.config
null check for mail config
2024-04-25 12:45:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
49184b287b null check for mail config 2024-04-25 12:45:21 -06:00
Hargata Softworks
f6139bda0d Merge pull request #502 from hargata/Hargata/mailkit.upgrade
fix inefficiencies
2024-04-25 11:33:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a6471b823b fix inefficiencies 2024-04-25 11:11:54 -06:00
Hargata Softworks
7c34003647 Merge pull request #501 from hargata/Hargata/mailkit.upgrade
MailKit Upgrade
2024-04-25 10:46:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1aa21f9980 Uprgade from .NET SMTPClient to MailKit as the default smtpclient does not support modern protocols. 2024-04-25 10:45:55 -06:00
Hargata Softworks
ce4ca50939 Merge pull request #499 from hargata/Hargata/persist.metric.bug
fix getyear method
2024-04-23 23:50:31 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fb28260c4a fix getyear method 2024-04-23 23:50:06 -06:00
Hargata Softworks
626a904747 Merge pull request #498 from hargata/Hargata/persist.metric.bug
Minor bug fix
2024-04-23 23:43:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
893cdafdc5 Fixes a very minor bug where the persisted year metric blanks out when viewing a different car that doesn't have that specific year. 2024-04-23 23:42:09 -06:00
Hargata Softworks
dbfb7d7d9c Merge pull request #493 from hargata/Hargata/persist.dashboard
check against null instead of undefined,
2024-04-15 08:42:43 -06:00
DESKTOP-GENO133\IvanPlex
a66538a7db check against null instead of undefined, 2024-04-15 08:41:53 -06:00
Hargata Softworks
2f77d87d4f Merge pull request #492 from hargata/Hargata/persist.dashboard
temporarily persist dashboard metrics in sessionStorage
2024-04-15 08:25:54 -06:00
DESKTOP-GENO133\IvanPlex
de85ba984c temporarily persist dashboard metrics in sessionStorage 2024-04-15 08:20:37 -06:00
Hargata Softworks
caac1a05ae Merge pull request #480 from hargata/Hargata/fix.link.color
fix donation link colors
2024-04-12 08:04:44 -06:00
Hargata Softworks
eb5793b819 Merge pull request #490 from hargata/Hargata/fix.alt.fuel
Fix Alternate Fuel Mileage Bug
2024-04-12 08:04:28 -06:00
DESKTOP-GENO133\IvanPlex
5ef3e1e2ce updated version number 2024-04-12 08:04:04 -06:00
DESKTOP-GENO133\IvanPlex
d8b459e5ee persist regional formatting when viewing record statistics. 2024-04-12 08:01:25 -06:00
DESKTOP-GENO133\IvanPlex
7b40d58aa1 fix alternate fuel mileage auto converting to NA decimal format bug. 2024-04-12 07:54:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
809e9b838e fix donation link colors 2024-04-09 21:29:19 -06:00
Hargata Softworks
23ae36ebd9 Merge pull request #475 from hargata/Hargata/update.readme
check if function exists.
2024-04-07 21:03:31 -06:00
DESKTOP-GENO133\IvanPlex
9c3f7d20f5 fix plans not updating when deleted. 2024-04-07 21:01:06 -06:00
DESKTOP-GENO133\IvanPlex
083298303c check if function exists. 2024-04-07 20:51:55 -06:00
Hargata Softworks
224970a07e Merge pull request #474 from hargata/Hargata/update.readme
Fix the problem with time.
2024-04-07 19:15:41 -06:00
DESKTOP-GENO133\IvanPlex
86d039e5b0 Fix the problem with time. 2024-04-07 17:59:08 -06:00
Hargata Softworks
6a8038aac9 Merge pull request #473 from hargata/Hargata/update.readme
update readme
2024-04-07 13:59:02 -06:00
DESKTOP-GENO133\IvanPlex
e748f08a8e update readme 2024-04-07 13:58:32 -06:00
Hargata Softworks
3580963e9f Merge pull request #472 from hargata/Hargata/planner.improvement.part2
Improved UI UX For Planner Edit
2024-04-07 12:51:28 -06:00
DESKTOP-GENO133\IvanPlex
b008ce2ab8 Improved UI UX 2024-04-07 12:50:28 -06:00
Hargata Softworks
ea0c2c7061 Merge pull request #471 from hargata/Hargata/transl
Updated translation
2024-04-07 11:26:35 -06:00
DESKTOP-GENO133\IvanPlex
0926220933 Updated translation 2024-04-07 11:26:12 -06:00
Hargata Softworks
ca975bbdd3 Merge pull request #470 from hargata/Hargata/planner.template.edit
updated translation
2024-04-07 11:22:20 -06:00
DESKTOP-GENO133\IvanPlex
b16c5c5302 updated translation 2024-04-07 11:21:43 -06:00
Hargata Softworks
cad05fe5d9 Merge pull request #466 from hargata/Hargata/planner.improvement
Moved template modal outside of add modal.
2024-04-07 11:20:42 -06:00
Hargata Softworks
a7cd466d9c Merge pull request #469 from hargata/Hargata/planner.template.edit
Add functionality to let users edit plan record templates
2024-04-07 11:20:28 -06:00
DESKTOP-GENO133\IvanPlex
4472a67ec0 fixed label function 2024-04-07 11:19:52 -06:00
DESKTOP-GENO133\IvanPlex
c84a4029ec basic functionality to edit templates 2024-04-07 11:09:05 -06:00
DESKTOP-GENO133\IvanPlex
d7d9ab505e Add functionality to let users edit plan record templates 2024-04-07 08:31:17 -06:00
DESKTOP-GENO133\IvanPlex
c582f5f5c7 Moved template modal outside of add modal. 2024-04-05 19:36:26 -06:00
Hargata Softworks
4c30939339 Merge pull request #465 from hargata/Hargata/unused.translation.key
Updated translation file and unsaved changes label color.
2024-04-05 18:50:02 -06:00
DESKTOP-GENO133\IvanPlex
cecd6a1d2b Updated translation file and unsaved changes label color. 2024-04-05 18:48:54 -06:00
Hargata Softworks
853dcbb364 Merge pull request #464 from hargata/Hargata/unused.translation.key
removed cached translation key
2024-04-05 07:18:08 -06:00
DESKTOP-GENO133\IvanPlex
ae327ed26d removed cached translation key 2024-04-05 07:17:49 -06:00
DESKTOP-GENO133\IvanPlex
3e416aa255 Revert "removed unused translation key"
This reverts commit ab98d02106faceaca1c6c76b8f0de7bf3e28cebe.
2024-04-05 07:16:11 -06:00
DESKTOP-GENO133\IvanPlex
61c2c3fc83 removed unused translation key 2024-04-05 07:16:10 -06:00
Hargata Softworks
ca749aaf1e Merge pull request #463 from hargata/Hargata/unsaved.changes
updated cached view to only show when there are unsaved changes present.
2024-04-05 07:11:50 -06:00
DESKTOP-GENO133\IvanPlex
2a2cb3bd0c updated cached view to only show when there are unsaved changes present. 2024-04-05 07:10:09 -06:00
Hargata Softworks
44e3d19844 Merge pull request #462 from hargata/Hargata/global.search
added incremental search
2024-04-04 19:16:22 -06:00
DESKTOP-GENO133\IvanPlex
1e25fffc70 added incremental search 2024-04-04 19:14:49 -06:00
Hargata Softworks
44645ed23d Merge pull request #461 from hargata/Hargata/global.search
added global search
2024-04-04 10:48:57 -06:00
DESKTOP-GENO133\IvanPlex
fa557e5f76 added global search 2024-04-04 10:46:43 -06:00
Hargata Softworks
7202fda38e Merge pull request #458 from hargata/Hargata/cached.records
Cached Record.
2024-04-02 21:53:46 -06:00
DESKTOP-GENO133\IvanPlex
d05afe41d6 Cache the record the user was viewing if they didn't save, delete, or move it. 2024-04-02 21:51:02 -06:00
Hargata Softworks
bfc0b58728 Merge pull request #453 from hargata/Hargata/bring.back.modal
Allow users to bring back modal window they accidentally closed.
2024-04-02 07:39:25 -06:00
DESKTOP-GENO133\IvanPlex
22e8aaca81 Allow users to bring back modal window they accidentally closed. 2024-04-02 07:29:16 -06:00
Hargata Softworks
5cb1247fb2 Merge pull request #452 from hargata/Hargata/odometer.increment
Odometer Increments
2024-04-01 18:31:27 -06:00
DESKTOP-GENO133\IvanPlex
17a6d99703 Allow users to increment for last reported odometer reading when creating new records. 2024-04-01 18:25:20 -06:00
Hargata Softworks
3e7917f767 Create FUNDING.yml 2024-04-01 09:14:46 -06:00
Hargata Softworks
e9277d4dd9 Merge pull request #443 from hargata/Hargata/reminder.icons
reminders will now display urgency in icons instead of a full badge w…
2024-03-29 09:35:17 -06:00
DESKTOP-GENO133\IvanPlex
058edd8af6 reminders will now display urgency in icons instead of a full badge when viewed in small screens. 2024-03-29 08:15:55 -06:00
Hargata Softworks
8ed7dcb9ff Merge pull request #441 from hargata/Hargata/reminder.mobile
Improved Reminder page usability on mobile
2024-03-28 19:15:49 -06:00
DESKTOP-GENO133\IvanPlex
1f4827abc0 Improved Reminder page usabilty on mobile 2024-03-28 19:13:19 -06:00
Hargata Softworks
9715a0fcf7 Merge pull request #440 from hargata/Hargata/vehicle.garage.tags
Added status tags
2024-03-27 20:22:44 -06:00
DESKTOP-GENO133\IvanPlex
9bf475c352 Added status tags 2024-03-27 20:16:56 -06:00
Hargata Softworks
c2aeb4bca0 Merge pull request #438 from hargata/Hargata/garage.status
Hargata/garage.status
2024-03-27 11:41:43 -06:00
DESKTOP-GENO133\IvanPlex
07b3020999 use current mileage and date when renewing reminders. 2024-03-27 11:39:30 -06:00
DESKTOP-GENO133\IvanPlex
c2a7f39025 add depreciation calculation 2024-03-27 09:24:33 -06:00
DESKTOP-GENO133\IvanPlex
a92d422972 add purchase and sold price 2024-03-27 08:21:02 -06:00
Hargata Softworks
345eb65c3a Merge pull request #437 from hargata/Hargata/copy.supplies.attachments
Option to copy supplies attachments into records.
2024-03-26 18:33:15 -06:00
DESKTOP-GENO133\IvanPlex
a459973983 Add functionality to copy over attachments from supplies to records that are utilizing them. 2024-03-26 18:23:16 -06:00
Hargata Softworks
470dd4d78a Merge pull request #435 from hargata/Hargata/reminder.tag
Reminder Tags
2024-03-26 08:47:14 -06:00
DESKTOP-GENO133\IvanPlex
e4cb183140 improve readability on urgent reminder labels, filtering by tag now updates aggregate counts. 2024-03-26 08:40:30 -06:00
DESKTOP-GENO133\IvanPlex
6455af96bf Add tag functionality to reminders. 2024-03-26 08:11:14 -06:00
Hargata Softworks
42afa87464 Merge pull request #432 from hargata/Hargata/multiple.reminders
added translation layer
2024-03-25 06:46:59 -06:00
DESKTOP-GENO133\IvanPlex
97466eeff2 added translation layer 2024-03-25 06:45:15 -06:00
Hargata Softworks
bcbfd4ba9c Merge pull request #426 from hargata/Hargata/multiple.reminders
Select Multiple Reminders when creating new record.
2024-03-23 20:55:04 -06:00
DESKTOP-GENO133\IvanPlex
cfa052fc31 Fix styling 2024-03-23 20:52:46 -06:00
DESKTOP-GENO133\IvanPlex
3f71f6a8d8 Updated version number. 2024-03-23 20:35:41 -06:00
DESKTOP-GENO133\IvanPlex
b70e442ca3 Allow users to select multiple reminders to push back when creating new record. 2024-03-23 20:32:23 -06:00
Hargata Softworks
1f60b2aadc Merge pull request #422 from hargata/Hargata/auto.oidc
Enable auto login via OIDC
2024-03-20 14:55:14 -06:00
DESKTOP-GENO133\IvanPlex
3d2117ddaf Enable auto login via OIDC 2024-03-20 14:53:51 -06:00
Hargata Softworks
bcff18ea58 Merge pull request #421 from hargata/Hargata/extrafield.links
Add URL parser to extra fields.
2024-03-20 14:35:12 -06:00
DESKTOP-GENO133\IvanPlex
2e9821402f Add URL parser to extra fields. 2024-03-20 13:54:51 -06:00
Hargata Softworks
8914c5cd51 Merge pull request #420 from hargata/Hargata/litedb.concurrency
Hargata/litedb.concurrency
2024-03-20 13:14:24 -06:00
DESKTOP-GENO133\IvanPlex
0b240498f9 Renamed LiteDBInjection to LiteDBHelper 2024-03-20 13:13:24 -06:00
DESKTOP-GENO133\IvanPlex
16f66364cf fix backup breaking error. 2024-03-20 13:04:15 -06:00
Hargata Softworks
f2b0cec427 Merge pull request #419 from hargata/Hargata/litedb.concurrency
Added DB Checkpoint for WAL
2024-03-20 11:44:23 -06:00
DESKTOP-GENO133\IvanPlex
fac05ff5c0 Added DB Checkpoint records. 2024-03-20 11:40:55 -06:00
Hargata Softworks
1ac6dfd2a6 Merge pull request #418 from hargata/Hargata/litedb.concurrency
LiteDB Concurrency
2024-03-20 10:44:39 -06:00
DESKTOP-GENO133\IvanPlex
8bcac7344f removed unused using. 2024-03-20 10:40:53 -06:00
DESKTOP-GENO133\IvanPlex
300c986abb Replaced LiteDB's initialization with every DB call with Dependency Injection instead. 2024-03-20 10:38:32 -06:00
Hargata Softworks
91a5f92df6 Merge pull request #415 from hargata/Hargata/cleanup
Fix security vulnerability with hosted files being accessible by unau…
2024-03-18 10:10:28 -06:00
DESKTOP-GENO133\IvanPlex
d60a09d48f Fix security vulnerability with hosted files being accessible by unauthorized users.
Note that this is a retrospecitve commit to credit the user with finding the vulnerability.

Co-authored-by: Julien Stebenne <julien.stebenne@gmail.com>
2024-03-18 10:08:34 -06:00
Hargata Softworks
b22bb7c7ad Merge pull request #414 from hargata/Hargata/cleanup
Hargata/cleanup
2024-03-18 09:21:56 -06:00
DESKTOP-GENO133\IvanPlex
63cddc4ab0 fixed security vulnerability. 2024-03-18 09:18:05 -06:00
DESKTOP-GENO133\IvanPlex
790061d5c4 cleaned up unused usings. 2024-03-17 18:33:05 -06:00
Hargata Softworks
6e4e2795b6 Merge pull request #406 from hargata/Hargata/multi.gas.edit
fixed ui bug
2024-03-16 12:24:55 -06:00
DESKTOP-GENO133\IvanPlex
d4e51b714d fixed ui bug 2024-03-16 12:23:46 -06:00
Hargata Softworks
6a164dc60b Merge pull request #405 from hargata/Hargata/multi.gas.edit
Added functionality to edit multiple gas records.
2024-03-16 12:11:52 -06:00
DESKTOP-GENO133\IvanPlex
ce602dcf66 Added functionality to edit multiple gas records. 2024-03-16 12:10:40 -06:00
Hargata Softworks
2facb1ab46 Merge pull request #402 from hargata/Hargata/further.improvements
More enhancements
2024-03-16 10:04:03 -06:00
DESKTOP-GENO133\IvanPlex
3c7d575c85 fixes and ensure that users no longer have to hard refresh their sites. 2024-03-16 10:01:48 -06:00
DESKTOP-GENO133\IvanPlex
9635c3c2c5 fixed bug where translations are not being backed up. 2024-03-16 09:44:27 -06:00
DESKTOP-GENO133\IvanPlex
72427fc19d fixed average calculation 2024-03-15 17:15:05 -06:00
DESKTOP-GENO133\IvanPlex
0bbd3c5491 fixed spelling 2024-03-15 17:09:51 -06:00
DESKTOP-GENO133\IvanPlex
871de4e75a updated translation 2024-03-15 17:07:01 -06:00
DESKTOP-GENO133\IvanPlex
bf984f280e added feature to make odometer adjustments a piece of cake. 2024-03-15 16:48:23 -06:00
DESKTOP-GENO133\IvanPlex
2b8f3cf13a properly capitalized web hook descriptions. 2024-03-15 16:21:41 -06:00
Hargata Softworks
1630a5c9ec Merge pull request #401 from hargata/Hargata/webhook
added web hooks
2024-03-15 15:12:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f17faa33f4 fixed punctuation 2024-03-15 15:09:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c245b848a0 reminder hooks 2024-03-15 15:06:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0ead9112c6 added vehicle deets 2024-03-15 15:01:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
44da393369 added web hooks 2024-03-15 14:59:30 -06:00
Hargata Softworks
5ae1628b7c Merge pull request #399 from hargata/Hargata/odo.modifier
Odometer Modifier
2024-03-15 14:05:41 -06:00
DESKTOP-GENO133\IvanPlex
59511d9ddd Updated version number 2024-03-15 12:49:34 -06:00
DESKTOP-GENO133\IvanPlex
82b0fba99a added functionality to modify odometer value when adding new odometer records. 2024-03-15 12:49:02 -06:00
DESKTOP-GENO133\IvanPlex
618107e515 added fields for odometer adjustments. 2024-03-15 10:16:28 -06:00
DESKTOP-GENO133\IvanPlex
35f931adf2 additional fields for vehicle level odometer settings. 2024-03-14 23:17:48 -06:00
Hargata Softworks
4a1e41cd54 Merge pull request #393 from hargata/Hargata/multi.odo.edit
Added functionality to edit multiple odometer records.
2024-03-13 10:18:13 -06:00
DESKTOP-T0O5CDB\DESK-555BD
888ec5cbbe Added functionality to edit multiple odometer records. 2024-03-13 10:16:04 -06:00
Hargata Softworks
f8f044d0cc Merge pull request #391 from hargata/Hargata/odo.changes
width issue
2024-03-11 11:42:26 -06:00
DESKTOP-GENO133\IvanPlex
36148c5539 width issue 2024-03-11 11:41:53 -06:00
Hargata Softworks
030dde0d64 Merge pull request #390 from hargata/Hargata/odo.changes
fixed admin panel text and button spacing.
2024-03-11 11:40:28 -06:00
DESKTOP-GENO133\IvanPlex
99faa951f9 fixed admin panel text and button spacing. 2024-03-11 11:39:56 -06:00
Hargata Softworks
7fb3a80ea3 Merge pull request #389 from hargata/Hargata/odo.changes
added support for meta key for MacOS users
2024-03-11 10:37:40 -06:00
DESKTOP-GENO133\IvanPlex
f277048aa9 added method to force recalculate distances 2024-03-11 10:34:42 -06:00
DESKTOP-GENO133\IvanPlex
34e3b8e145 added support for meta key for MacOS users 2024-03-11 10:01:24 -06:00
Hargata Softworks
91e8526b8e Merge pull request #388 from hargata/Hargata/odo.changes
removed logic that can cause bad data.
2024-03-10 14:19:54 -06:00
DESKTOP-GENO133\IvanPlex
926188ef64 removed logic that can cause bad data. 2024-03-10 14:18:24 -06:00
Hargata Softworks
1b5fc6729e Merge pull request #387 from hargata/Hargata/odo.changes
Odometer Changes
2024-03-10 14:13:35 -06:00
DESKTOP-GENO133\IvanPlex
3ac53ea144 added method to calculate distance. 2024-03-10 11:59:38 -06:00
DESKTOP-GENO133\IvanPlex
4dc3b4f741 fixed csv import 2024-03-10 09:05:15 -06:00
DESKTOP-GENO133\IvanPlex
7c1e6bd45c made API endpoints copyable 2024-03-10 08:56:41 -06:00
DESKTOP-GENO133\IvanPlex
00b3145e79 display distance traveled. 2024-03-10 08:02:44 -06:00
DESKTOP-GENO133\IvanPlex
def9a7770f odometer to trips improvement. 2024-03-10 06:33:39 -06:00
Hargata Softworks
5ba0bd5771 Merge pull request #381 from hargata/Hargata/fix.print
7z
2024-03-08 16:19:54 -07:00
DESKTOP-T0O5CDB\DESK-555BD
adc2de07f7 7z 2024-03-08 16:19:36 -07:00
Hargata Softworks
e12f528635 Merge pull request #380 from hargata/Hargata/fix.print
fixed the date column.
2024-03-08 16:17:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c96ac53b91 fixed the date column. 2024-03-08 16:17:20 -07:00
Hargata Softworks
8c15bd5534 Merge pull request #378 from hargata/Hargata/fix.print
Improved Attachment Export
2024-03-08 11:49:57 -07:00
DESKTOP-T0O5CDB\DESK-555BD
90755d68b3 Improved Attachment Export 2024-03-08 11:49:30 -07:00
Hargata Softworks
94dfe9a0bb Merge pull request #377 from hargata/Hargata/fix.print
Added flex shrink
2024-03-08 11:13:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9dbcf71856 Added flex shrink 2024-03-08 11:11:37 -07:00
Hargata Softworks
728fc3593f Merge pull request #374 from hargata/Hargata/fix.print
Fixed black border issue when printing
2024-03-07 22:49:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1347585af2 Fixed black border issue when printing 2024-03-07 22:49:11 -07:00
134 changed files with 3557 additions and 11555 deletions

1
.env
View File

@@ -2,7 +2,6 @@ LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8
MailConfig__EmailServer=""
MailConfig__EmailFrom=""
MailConfig__UseSSL="false"
MailConfig__Port=587
MailConfig__Username=""
MailConfig__Password=""

14
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: lubelogger
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ wwwroot/translations/
config/userConfig.json
CarCareTracker.csproj.user
Properties/launchSettings.json
data/cartracker-log.db

View File

@@ -13,7 +13,8 @@
<ItemGroup>
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Npgsql" Version="8.0.2" />
<PackageReference Include="MailKit" Version="4.5.0" />
<PackageReference Include="Npgsql" Version="8.0.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
</ItemGroup>

View File

@@ -1,5 +1,4 @@
using CarCareTracker.External.Implementations;
using CarCareTracker.External.Interfaces;
using CarCareTracker.External.Interfaces;
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Logic;
@@ -27,6 +26,8 @@ namespace CarCareTracker.Controllers
private readonly IReminderHelper _reminderHelper;
private readonly IGasHelper _gasHelper;
private readonly IUserLogic _userLogic;
private readonly IVehicleLogic _vehicleLogic;
private readonly IOdometerLogic _odometerLogic;
private readonly IFileHelper _fileHelper;
private readonly IMailHelper _mailHelper;
private readonly IConfigHelper _config;
@@ -46,7 +47,9 @@ namespace CarCareTracker.Controllers
IMailHelper mailHelper,
IFileHelper fileHelper,
IConfigHelper config,
IUserLogic userLogic)
IUserLogic userLogic,
IVehicleLogic vehicleLogic,
IOdometerLogic odometerLogic)
{
_dataAccess = dataAccess;
_noteDataAccess = noteDataAccess;
@@ -63,6 +66,8 @@ namespace CarCareTracker.Controllers
_gasHelper = gasHelper;
_reminderHelper = reminderHelper;
_userLogic = userLogic;
_odometerLogic = odometerLogic;
_vehicleLogic = vehicleLogic;
_fileHelper = fileHelper;
_config = config;
}
@@ -138,8 +143,9 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Service Record via API - Description: {serviceRecord.Description}");
response.Success = true;
response.Message = "Service Record Added";
return Json(response);
@@ -205,8 +211,9 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Repair Record via API - Description: {repairRecord.Description}");
response.Success = true;
response.Message = "Repair Record Added";
return Json(response);
@@ -272,8 +279,9 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Upgrade Record via API - Description: {upgradeRecord.Description}");
response.Success = true;
response.Message = "Upgrade Record Added";
return Json(response);
@@ -327,6 +335,7 @@ namespace CarCareTracker.Controllers
Cost = decimal.Parse(input.Cost)
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
response.Success = true;
response.Message = "Tax Record Added";
return Json(response);
@@ -344,7 +353,7 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/odometerrecords/latest")]
public IActionResult LastOdometer(int vehicleId)
{
var result = GetMaxMileage(vehicleId);
var result = _vehicleLogic.GetMaxMileage(vehicleId);
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -353,7 +362,12 @@ namespace CarCareTracker.Controllers
public IActionResult OdometerRecords(int vehicleId)
{
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), Odometer = x.Mileage.ToString(), Notes = x.Notes });
//determine if conversion is needed.
if (vehicleRecords.All(x => x.InitialMileage == default))
{
vehicleRecords = _odometerLogic.AutoConvertOdometerRecord(vehicleRecords);
}
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes });
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -384,9 +398,11 @@ namespace CarCareTracker.Controllers
VehicleId = vehicleId,
Date = DateTime.Parse(input.Date),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
InitialMileage = (string.IsNullOrWhiteSpace(input.InitialOdometer) || int.Parse(input.InitialOdometer) == default) ? _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()) : int.Parse(input.InitialOdometer),
Mileage = int.Parse(input.Odometer)
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
response.Success = true;
response.Message = "Odometer Record Added";
return Json(response);
@@ -466,8 +482,9 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Mileage = int.Parse(input.Odometer)
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Gas record via API - Mileage: {gasRecord.Mileage.ToString()}");
response.Success = true;
response.Message = "Gas Record Added";
return Json(response);
@@ -485,7 +502,7 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/reminders")]
public IActionResult Reminders(int vehicleId)
{
var currentMileage = GetMaxMileage(vehicleId);
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes});
return Json(results);
@@ -501,7 +518,7 @@ namespace CarCareTracker.Controllers
{
var vehicleId = vehicle.Id;
//get reminders
var currentMileage = GetMaxMileage(vehicleId);
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).OrderByDescending(x => x.Urgency).ToList();
results.RemoveAll(x => !urgencies.Contains(x.Urgency));
@@ -551,35 +568,5 @@ namespace CarCareTracker.Controllers
var result = _fileHelper.RestoreBackup("/defaults/demo_default.zip", true);
return Json(result);
}
private int GetMaxMileage(int vehicleId)
{
var numbersArray = new List<int>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
if (serviceRecords.Any())
{
numbersArray.Add(serviceRecords.Max(x => x.Mileage));
}
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
if (repairRecords.Any())
{
numbersArray.Add(repairRecords.Max(x => x.Mileage));
}
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
if (gasRecords.Any())
{
numbersArray.Add(gasRecords.Max(x => x.Mileage));
}
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
if (upgradeRecords.Any())
{
numbersArray.Add(upgradeRecords.Max(x => x.Mileage));
}
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
if (odometerRecords.Any())
{
numbersArray.Add(odometerRecords.Max(x => x.Mileage));
}
return numbersArray.Any() ? numbersArray.Max() : 0;
}
}
}

View File

@@ -16,6 +16,7 @@ namespace CarCareTracker.Controllers
private readonly IVehicleDataAccess _dataAccess;
private readonly IUserLogic _userLogic;
private readonly ILoginLogic _loginLogic;
private readonly IVehicleLogic _vehicleLogic;
private readonly IFileHelper _fileHelper;
private readonly IConfigHelper _config;
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
@@ -25,6 +26,7 @@ namespace CarCareTracker.Controllers
IVehicleDataAccess dataAccess,
IUserLogic userLogic,
ILoginLogic loginLogic,
IVehicleLogic vehicleLogic,
IConfigHelper configuration,
IFileHelper fileHelper,
IExtraFieldDataAccess extraFieldDataAccess,
@@ -40,6 +42,7 @@ namespace CarCareTracker.Controllers
_reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper;
_loginLogic = loginLogic;
_vehicleLogic = vehicleLogic;
}
private int GetUserID()
{
@@ -56,7 +59,23 @@ namespace CarCareTracker.Controllers
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
return PartialView("_GarageDisplay", vehiclesStored);
var vehicleViewModels = vehiclesStored.Select(x => new VehicleViewModel
{
Id = x.Id,
ImageLocation = x.ImageLocation,
Year = x.Year,
Make = x.Make,
Model = x.Model,
LicensePlate = x.LicensePlate,
SoldDate = x.SoldDate,
IsElectric = x.IsElectric,
UseHours = x.UseHours,
ExtraFields = x.ExtraFields,
Tags = x.Tags,
LastReportedMileage = _vehicleLogic.GetMaxMileage(x.Id),
HasReminders = _vehicleLogic.GetVehicleHasUrgentOrPastDueReminders(x.Id)
}).ToList();
return PartialView("_GarageDisplay", vehicleViewModels);
}
public IActionResult Calendar()
{
@@ -86,7 +105,7 @@ namespace CarCareTracker.Controllers
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(new List<ReminderRecord> { reminder }, 0, DateTime.Now).FirstOrDefault();
return PartialView("_ReminderRecordCalendarModal", reminderUrgency);
}
public IActionResult Settings()
public async Task<IActionResult> Settings()
{
var userConfig = _config.GetUserConfig(User);
var languages = _fileHelper.GetLanguages();
@@ -95,6 +114,16 @@ namespace CarCareTracker.Controllers
UserConfig = userConfig,
UILanguages = languages
};
try
{
var httpClient = new HttpClient();
var sponsorsData = await httpClient.GetFromJsonAsync<Sponsors>(StaticHelper.SponsorsPath) ?? new Sponsors();
viewModel.Sponsors = sponsorsData;
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve sponsors: {ex.Message}");
}
return PartialView("_Settings", viewModel);
}
[HttpPost]
@@ -190,6 +219,13 @@ namespace CarCareTracker.Controllers
var userName = User.Identity.Name;
return PartialView("_AccountModal", new UserData() { EmailAddress = emailAddress, UserName = userName });
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetRootAccountInformationModal()
{
var userName = User.Identity.Name;
return PartialView("_RootAccountModal", new UserData() { UserName = userName });
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{

View File

@@ -29,6 +29,18 @@ namespace CarCareTracker.Controllers
}
public IActionResult Index(string redirectURL = "")
{
var remoteAuthConfig = _config.GetOpenIDConfig();
if (remoteAuthConfig.DisableRegularLogin && !string.IsNullOrWhiteSpace(remoteAuthConfig.LogOutURL))
{
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
remoteAuthConfig.State = generatedState;
if (remoteAuthConfig.ValidateState)
{
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
return Redirect(remoteAuthURL);
}
return View(model: redirectURL);
}
public IActionResult Registration()
@@ -208,7 +220,7 @@ namespace CarCareTracker.Controllers
var result = _loginLogic.ResetPasswordByUser(credentials);
return Json(result);
}
[Authorize] //User must already be logged in to do this.
[Authorize(Roles = nameof(UserData.IsRootUser))] //User must already be logged in as root user to do this.
[HttpPost]
public IActionResult CreateLoginCreds(LoginModel credentials)
{
@@ -223,7 +235,7 @@ namespace CarCareTracker.Controllers
}
return Json(false);
}
[Authorize]
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult DestroyLoginCreds()
{
@@ -248,7 +260,12 @@ namespace CarCareTracker.Controllers
public IActionResult LogOut()
{
Response.Cookies.Delete("ACCESS_TOKEN");
return Json(true);
var remoteAuthConfig = _config.GetOpenIDConfig();
if (remoteAuthConfig.DisableRegularLogin && !string.IsNullOrWhiteSpace(remoteAuthConfig.LogOutURL))
{
return Json(remoteAuthConfig.LogOutURL);
}
return Json("/Login");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,51 +7,48 @@ namespace CarCareTracker.External.Implementations
{
public class CollisionRecordDataAccess : ICollisionRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "collisionrecords";
public CollisionRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<CollisionRecord> GetCollisionRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
var collisionRecords = table.Find(Query.EQ(nameof(CollisionRecord.VehicleId), vehicleId));
return collisionRecords.ToList() ?? new List<CollisionRecord>();
};
}
public CollisionRecord GetCollisionRecordById(int collisionRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
return table.FindById(collisionRecordId);
};
}
public bool DeleteCollisionRecordById(int collisionRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
table.Delete(collisionRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveCollisionRecordToVehicle(CollisionRecord collisionRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
table.Upsert(collisionRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllCollisionRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<CollisionRecord>(tableName);
var collisionRecords = table.DeleteMany(Query.EQ(nameof(CollisionRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,30 +1,30 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class ExtraFieldDataAccess: IExtraFieldDataAccess
public class ExtraFieldDataAccess : IExtraFieldDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "extrafields";
public ExtraFieldDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public RecordExtraField GetExtraFieldsById(int importMode)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<RecordExtraField>(tableName);
return table.FindById(importMode) ?? new RecordExtraField();
};
}
public bool SaveExtraFields(RecordExtraField record)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<RecordExtraField>(tableName);
table.Upsert(record);
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -5,53 +5,50 @@ using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class GasRecordDataAccess: IGasRecordDataAccess
public class GasRecordDataAccess : IGasRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "gasrecords";
public GasRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<GasRecord> GetGasRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
var gasRecords = table.Find(Query.EQ(nameof(GasRecord.VehicleId), vehicleId));
return gasRecords.ToList() ?? new List<GasRecord>();
};
}
public GasRecord GetGasRecordById(int gasRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
return table.FindById(gasRecordId);
};
}
public bool DeleteGasRecordById(int gasRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
table.Delete(gasRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveGasRecordToVehicle(GasRecord gasRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
table.Upsert(gasRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllGasRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<GasRecord>(tableName);
var gasRecords = table.DeleteMany(Query.EQ(nameof(GasRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,57 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class NoteDataAccess: INoteDataAccess
public class NoteDataAccess : INoteDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "notes";
public NoteDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<Note> GetNotesByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
var noteToReturn = table.Find(Query.EQ(nameof(Note.VehicleId), vehicleId));
return noteToReturn.ToList() ?? new List<Note>();
};
}
public Note GetNoteById(int noteId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
return table.FindById(noteId);
};
}
public bool SaveNoteToVehicle(Note note)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
table.Upsert(note);
db.Checkpoint();
return true;
};
}
public bool DeleteNoteById(int noteId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
table.Delete(noteId);
db.Checkpoint();
return true;
};
}
public bool DeleteAllNotesByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Note>(tableName);
var notes = table.DeleteMany(Query.EQ(nameof(Note.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,57 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class OdometerRecordDataAccess : IOdometerRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "odometerrecords";
public OdometerRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<OdometerRecord> GetOdometerRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
var odometerRecords = table.Find(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
return odometerRecords.ToList() ?? new List<OdometerRecord>();
};
}
public OdometerRecord GetOdometerRecordById(int odometerRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
return table.FindById(odometerRecordId);
};
}
public bool DeleteOdometerRecordById(int odometerRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
table.Delete(odometerRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveOdometerRecordToVehicle(OdometerRecord odometerRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
table.Upsert(odometerRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllOdometerRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<OdometerRecord>(tableName);
var odometerRecords = table.DeleteMany(Query.EQ(nameof(OdometerRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,57 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class PlanRecordDataAccess : IPlanRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "planrecords";
public PlanRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<PlanRecord> GetPlanRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.Find(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
return planRecords.ToList() ?? new List<PlanRecord>();
};
}
public PlanRecord GetPlanRecordById(int planRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
return table.FindById(planRecordId);
};
}
public bool DeletePlanRecordById(int planRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
table.Delete(planRecordId);
db.Checkpoint();
return true;
};
}
public bool SavePlanRecordToVehicle(PlanRecord planRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
table.Upsert(planRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllPlanRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(PlanRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,57 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class PlanRecordTemplateDataAccess : IPlanRecordTemplateDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "planrecordtemplates";
public PlanRecordTemplateDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<PlanRecordInput> GetPlanRecordTemplatesByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
var planRecords = table.Find(Query.EQ(nameof(PlanRecordInput.VehicleId), vehicleId));
return planRecords.ToList() ?? new List<PlanRecordInput>();
};
}
public PlanRecordInput GetPlanRecordTemplateById(int planRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
return table.FindById(planRecordId);
};
}
public bool DeletePlanRecordTemplateById(int planRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
table.Delete(planRecordId);
db.Checkpoint();
return true;
};
}
public bool SavePlanRecordTemplateToVehicle(PlanRecordInput planRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecordInput>(tableName);
table.Upsert(planRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllPlanRecordTemplatesByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<PlanRecord>(tableName);
var planRecords = table.DeleteMany(Query.EQ(nameof(PlanRecordInput.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,57 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class ReminderRecordDataAccess : IReminderRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "reminderrecords";
public ReminderRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<ReminderRecord> GetReminderRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
var reminderRecords = table.Find(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
return reminderRecords.ToList() ?? new List<ReminderRecord>();
};
}
public ReminderRecord GetReminderRecordById(int reminderRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
return table.FindById(reminderRecordId);
};
}
public bool DeleteReminderRecordById(int reminderRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
table.Delete(reminderRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveReminderRecordToVehicle(ReminderRecord reminderRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
table.Upsert(reminderRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllReminderRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ReminderRecord>(tableName);
var reminderRecords = table.DeleteMany(Query.EQ(nameof(ReminderRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,57 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class ServiceRecordDataAccess: IServiceRecordDataAccess
public class ServiceRecordDataAccess : IServiceRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "servicerecords";
public ServiceRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<ServiceRecord> GetServiceRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
var serviceRecords = table.Find(Query.EQ(nameof(ServiceRecord.VehicleId), vehicleId));
return serviceRecords.ToList() ?? new List<ServiceRecord>();
};
}
public ServiceRecord GetServiceRecordById(int serviceRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
return table.FindById(serviceRecordId);
};
}
public bool DeleteServiceRecordById(int serviceRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
table.Delete(serviceRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveServiceRecordToVehicle(ServiceRecord serviceRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
table.Upsert(serviceRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllServiceRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<ServiceRecord>(tableName);
var serviceRecords = table.DeleteMany(Query.EQ(nameof(ServiceRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -7,51 +7,48 @@ namespace CarCareTracker.External.Implementations
{
public class SupplyRecordDataAccess : ISupplyRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "supplyrecords";
public SupplyRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<SupplyRecord> GetSupplyRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
var supplyRecords = table.Find(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
return supplyRecords.ToList() ?? new List<SupplyRecord>();
};
}
public SupplyRecord GetSupplyRecordById(int supplyRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
return table.FindById(supplyRecordId);
};
}
public bool DeleteSupplyRecordById(int supplyRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
table.Delete(supplyRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveSupplyRecordToVehicle(SupplyRecord supplyRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
table.Upsert(supplyRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllSupplyRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<SupplyRecord>(tableName);
var supplyRecords = table.DeleteMany(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,57 +1,54 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using CarCareTracker.Helper;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class TaxRecordDataAccess : ITaxRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "taxrecords";
public TaxRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<TaxRecord> GetTaxRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
var taxRecords = table.Find(Query.EQ(nameof(TaxRecord.VehicleId), vehicleId));
return taxRecords.ToList() ?? new List<TaxRecord>();
};
}
public TaxRecord GetTaxRecordById(int taxRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
return table.FindById(taxRecordId);
};
}
public bool DeleteTaxRecordById(int taxRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
table.Delete(taxRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveTaxRecordToVehicle(TaxRecord taxRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
table.Upsert(taxRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllTaxRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<TaxRecord>(tableName);
var taxRecords = table.DeleteMany(Query.EQ(nameof(TaxRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -7,51 +7,47 @@ namespace CarCareTracker.External.Implementations
{
public class TokenRecordDataAccess : ITokenRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "tokenrecords";
public TokenRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<Token> GetTokens()
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
return table.FindAll().ToList();
};
}
public Token GetTokenRecordByBody(string tokenBody)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
var tokenRecord = table.FindOne(Query.EQ(nameof(Token.Body), tokenBody));
return tokenRecord ?? new Token();
};
}
public Token GetTokenRecordByEmailAddress(string emailAddress)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
var tokenRecord = table.FindOne(Query.EQ(nameof(Token.EmailAddress), emailAddress));
return tokenRecord ?? new Token();
};
}
public bool CreateNewToken(Token token)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
table.Insert(token);
db.Checkpoint();
return true;
};
}
public bool DeleteToken(int tokenId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Token>(tableName);
table.Delete(tokenId);
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -7,51 +7,48 @@ namespace CarCareTracker.External.Implementations
{
public class UpgradeRecordDataAccess : IUpgradeRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
public UpgradeRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
private static string tableName = "upgraderecords";
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
var upgradeRecords = table.Find(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
return upgradeRecords.ToList() ?? new List<UpgradeRecord>();
};
}
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
return table.FindById(upgradeRecordId);
};
}
public bool DeleteUpgradeRecordById(int upgradeRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
table.Delete(upgradeRecordId);
db.Checkpoint();
return true;
};
}
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
table.Upsert(upgradeRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UpgradeRecord>(tableName);
var upgradeRecords = table.DeleteMany(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -7,8 +7,12 @@ namespace CarCareTracker.External.Implementations
{
public class UserAccessDataAccess : IUserAccessDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "useraccessrecords";
public UserAccessDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
/// <summary>
/// Gets a list of vehicles user have access to.
/// </summary>
@@ -16,45 +20,37 @@ namespace CarCareTracker.External.Implementations
/// <returns></returns>
public List<UserAccess> GetUserAccessByUserId(int userId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
return table.Find(x=>x.Id.UserId == userId).ToList();
};
return table.Find(x => x.Id.UserId == userId).ToList();
}
public UserAccess GetUserAccessByVehicleAndUserId(int userId, int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
return table.Find(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId).FirstOrDefault();
};
}
public List<UserAccess> GetUserAccessByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
return table.Find(x => x.Id.VehicleId == vehicleId).ToList();
};
}
public bool SaveUserAccess(UserAccess userAccess)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.Upsert(userAccess);
db.Checkpoint();
return true;
};
}
public bool DeleteUserAccess(int userId, int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.DeleteMany(x => x.Id.UserId == userId && x.Id.VehicleId == vehicleId);
db.Checkpoint();
return true;
};
}
/// <summary>
/// Delete all access records when a vehicle is deleted.
@@ -63,12 +59,11 @@ namespace CarCareTracker.External.Implementations
/// <returns></returns>
public bool DeleteAllAccessRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.DeleteMany(x=>x.Id.VehicleId == vehicleId);
table.DeleteMany(x => x.Id.VehicleId == vehicleId);
db.Checkpoint();
return true;
};
}
/// <summary>
/// Delee all access records when a user is deleted.
@@ -77,12 +72,11 @@ namespace CarCareTracker.External.Implementations
/// <returns></returns>
public bool DeleteAllAccessRecordsByUserId(int userId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserAccess>(tableName);
table.DeleteMany(x => x.Id.UserId == userId);
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,39 +1,38 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
using CarCareTracker.Helper;
namespace CarCareTracker.External.Implementations
{
public class UserConfigDataAccess: IUserConfigDataAccess
public class UserConfigDataAccess : IUserConfigDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "userconfigrecords";
public UserConfigDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public UserConfigData GetUserConfig(int userId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserConfigData>(tableName);
return table.FindById(userId);
};
}
public bool SaveUserConfig(UserConfigData userConfigData)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserConfigData>(tableName);
table.Upsert(userConfigData);
db.Checkpoint();
return true;
};
}
public bool DeleteUserConfig(int userId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserConfigData>(tableName);
table.Delete(userId);
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -1,66 +1,60 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
using CarCareTracker.Helper;
namespace CarCareTracker.External.Implementations
{
public class UserRecordDataAccess : IUserRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "userrecords";
public UserRecordDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public List<UserData> GetUsers()
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
return table.FindAll().ToList();
};
}
public UserData GetUserRecordByUserName(string userName)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
var userRecord = table.FindOne(Query.EQ(nameof(UserData.UserName), userName));
return userRecord ?? new UserData();
};
}
public UserData GetUserRecordByEmailAddress(string emailAddress)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
var userRecord = table.FindOne(Query.EQ(nameof(UserData.EmailAddress), emailAddress));
return userRecord ?? new UserData();
};
}
public UserData GetUserRecordById(int userId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
var userRecord = table.FindById(userId);
return userRecord ?? new UserData();
};
}
public bool SaveUserRecord(UserData userRecord)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
table.Upsert(userRecord);
db.Checkpoint();
return true;
};
}
public bool DeleteUserRecord(int userId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<UserData>(tableName);
table.Delete(userId);
db.Checkpoint();
return true;
};
}
}
}

View File

@@ -5,42 +5,41 @@ using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class VehicleDataAccess: IVehicleDataAccess
public class VehicleDataAccess : IVehicleDataAccess
{
private static string dbName = StaticHelper.DbName;
private ILiteDBHelper _liteDB { get; set; }
private static string tableName = "vehicles";
public VehicleDataAccess(ILiteDBHelper liteDB)
{
_liteDB = liteDB;
}
public bool SaveVehicle(Vehicle vehicle)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
var result = table.Upsert(vehicle);
db.Checkpoint();
return true;
};
}
public bool DeleteVehicle(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
return table.Delete(vehicleId);
};
var result = table.Delete(vehicleId);
db.Checkpoint();
return result;
}
public List<Vehicle> GetVehicles()
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
return table.FindAll().ToList();
};
}
public Vehicle GetVehicleById(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<Vehicle>(tableName);
return table.FindById(vehicleId);
};
}
}
}

View File

@@ -12,6 +12,7 @@ namespace CarCareTracker.Helper
UserConfig GetUserConfig(ClaimsPrincipal user);
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
bool AuthenticateRootUser(string username, string password);
string GetWebHookUrl();
string GetMOTD();
string GetLogoUrl();
string GetServerLanguage();
@@ -33,6 +34,15 @@ namespace CarCareTracker.Helper
_userConfig = userConfig;
_cache = memoryCache;
}
public string GetWebHookUrl()
{
var webhook = _config["LUBELOGGER_WEBHOOK"];
if (string.IsNullOrWhiteSpace(webhook))
{
webhook = "";
}
return webhook;
}
public string GetMOTD()
{
var motd = _config["LUBELOGGER_MOTD"];

View File

@@ -18,10 +18,12 @@ namespace CarCareTracker.Helper
{
private readonly IWebHostEnvironment _webEnv;
private readonly ILogger<IFileHelper> _logger;
public FileHelper(IWebHostEnvironment webEnv, ILogger<IFileHelper> logger)
private ILiteDBHelper _liteDB;
public FileHelper(IWebHostEnvironment webEnv, ILogger<IFileHelper> logger, ILiteDBHelper liteDB)
{
_webEnv = webEnv;
_logger = logger;
_liteDB = liteDB;
}
public List<string> GetLanguages()
{
@@ -93,6 +95,7 @@ namespace CarCareTracker.Helper
//copy over images and documents.
var imagePath = Path.Combine(tempPath, "images");
var documentPath = Path.Combine(tempPath, "documents");
var translationPath = Path.Combine(tempPath, "translations");
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
if (Directory.Exists(imagePath))
@@ -139,8 +142,32 @@ namespace CarCareTracker.Helper
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
}
}
if (Directory.Exists(translationPath))
{
var existingPath = Path.Combine(_webEnv.WebRootPath, "translations");
if (!Directory.Exists(existingPath))
{
Directory.CreateDirectory(existingPath);
}
else if (clearExisting)
{
var filesToDelete = Directory.GetFiles(existingPath);
foreach (string file in filesToDelete)
{
File.Delete(file);
}
}
//copy each files from temp folder to newPath
var filesToUpload = Directory.GetFiles(translationPath);
foreach (string file in filesToUpload)
{
File.Copy(file, $"{existingPath}/{Path.GetFileName(file)}", true);
}
}
if (File.Exists(dataPath))
{
//Relinquish current DB file lock
_liteDB.DisposeLiteDB();
//data path will always exist as it is created on startup if not.
File.Move(dataPath, StaticHelper.DbName, true);
}
@@ -173,7 +200,7 @@ namespace CarCareTracker.Helper
foreach (UploadedFiles file in reportModel.Files)
{
var fileToCopy = GetFullFilePath(file.Location);
var destFileName = $"{tempPath}/{fileIndex}{Path.GetExtension(file.Location)}";
var destFileName = $"{tempPath}/{fileIndex}_{reportModel.DataType}_{reportModel.Date.ToString("yyyy-MM-dd")}_{file.Name}{Path.GetExtension(file.Location)}";
File.Copy(fileToCopy, destFileName);
fileIndex++;
}
@@ -191,6 +218,7 @@ namespace CarCareTracker.Helper
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
var dataPath = StaticHelper.DbName;
var configPath = StaticHelper.UserConfigPath;
if (!Directory.Exists(tempPath))
@@ -215,6 +243,16 @@ namespace CarCareTracker.Helper
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
}
}
if (Directory.Exists(translationPath))
{
var files = Directory.GetFiles(translationPath);
foreach(var file in files)
{
var newPath = Path.Combine(tempPath, "translations");
Directory.CreateDirectory(newPath);
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
}
}
if (File.Exists(dataPath))
{
var newPath = Path.Combine(tempPath, "data");

36
Helper/LiteDBHelper.cs Normal file
View File

@@ -0,0 +1,36 @@
using LiteDB;
namespace CarCareTracker.Helper;
public interface ILiteDBHelper
{
LiteDatabase GetLiteDB();
void DisposeLiteDB();
}
public class LiteDBHelper: ILiteDBHelper
{
public LiteDatabase db { get; set; }
public LiteDBHelper()
{
if (db == null)
{
db = new LiteDatabase(StaticHelper.DbName);
}
}
public LiteDatabase GetLiteDB()
{
if (db == null)
{
db = new LiteDatabase(StaticHelper.DbName);
}
return db;
}
public void DisposeLiteDB()
{
if (db != null)
{
db.Dispose();
db = null;
}
}
}

View File

@@ -1,6 +1,6 @@
using CarCareTracker.Models;
using System.Net.Mail;
using System.Net;
using MimeKit;
using MailKit.Net.Smtp;
namespace CarCareTracker.Helper
{
@@ -15,13 +15,16 @@ namespace CarCareTracker.Helper
{
private readonly MailConfig mailConfig;
private readonly IFileHelper _fileHelper;
private readonly ILogger<MailHelper> _logger;
public MailHelper(
IConfiguration config,
IFileHelper fileHelper
IFileHelper fileHelper,
ILogger<MailHelper> logger
) {
//load mailConfig from Configuration
mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
mailConfig = config.GetSection("MailConfig").Get<MailConfig>() ?? new MailConfig();
_fileHelper = fileHelper;
_logger = logger;
}
public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
{
@@ -34,7 +37,7 @@ namespace CarCareTracker.Helper
}
string emailSubject = "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}";
var result = SendEmail(emailAddress, emailSubject, emailBody);
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
@@ -55,7 +58,7 @@ namespace CarCareTracker.Helper
}
string emailSubject = "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}";
var result = SendEmail(emailAddress, emailSubject, emailBody);
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
@@ -77,7 +80,7 @@ namespace CarCareTracker.Helper
}
string emailSubject = "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}";
var result = SendEmail(emailAddress, emailSubject, emailBody);
var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
@@ -116,44 +119,49 @@ namespace CarCareTracker.Helper
emailBody = emailBody.Replace("{TableBody}", tableBody);
try
{
foreach (string emailAddress in emailAddresses)
{
SendEmail(emailAddress, emailSubject, emailBody, true, true);
}
SendEmail(emailAddresses, emailSubject, emailBody);
return new OperationResponse { Success = true, Message = "Email Sent!" };
} catch (Exception ex)
{
return new OperationResponse { Success = false, Message = ex.Message };
}
}
private bool SendEmail(string emailTo, string emailSubject, string emailBody, bool isBodyHtml = false, bool useAsync = false) {
string to = emailTo;
private bool SendEmail(List<string> emailTo, string emailSubject, string emailBody) {
string from = mailConfig.EmailFrom;
var server = mailConfig.EmailServer;
MailMessage message = new MailMessage(from, to);
var message = new MimeMessage();
message.From.Add(new MailboxAddress(from, from));
foreach(string emailRecipient in emailTo)
{
message.To.Add(new MailboxAddress(emailRecipient, emailRecipient));
}
message.Subject = emailSubject;
message.Body = emailBody;
message.IsBodyHtml = isBodyHtml;
SmtpClient client = new SmtpClient(server);
client.EnableSsl = mailConfig.UseSSL;
client.Port = mailConfig.Port;
client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password);
var builder = new BodyBuilder();
builder.HtmlBody = emailBody;
message.Body = builder.ToMessageBody();
using (var client = new SmtpClient())
{
client.Connect(server, mailConfig.Port, MailKit.Security.SecureSocketOptions.Auto);
//perform authentication if either username or password is provided.
//do not perform authentication if neither are provided.
if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) {
client.Authenticate(mailConfig.Username, mailConfig.Password);
}
try
{
if (useAsync)
{
client.SendMailAsync(message, new CancellationToken());
}
else
{
client.Send(message);
}
client.Disconnect(true);
return true;
}
catch (Exception ex)
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}
}

View File

@@ -4,7 +4,7 @@ namespace CarCareTracker.Helper
{
public interface IReminderHelper
{
ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder);
ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, int? currentMileage);
List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare);
}
public class ReminderHelper: IReminderHelper
@@ -14,46 +14,48 @@ namespace CarCareTracker.Helper
{
_config = config;
}
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder)
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, int? currentMileage)
{
var newDate = currentDate ?? existingReminder.Date;
var newMileage = currentMileage ?? existingReminder.Mileage;
if (existingReminder.Metric == ReminderMetric.Both)
{
if (existingReminder.ReminderMonthInterval != ReminderMonthInterval.Other)
{
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval);
} else
{
existingReminder.Date = existingReminder.Date.AddMonths(existingReminder.CustomMonthInterval);
existingReminder.Date = newDate.Date.AddMonths(existingReminder.CustomMonthInterval);
}
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
{
existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval;
existingReminder.Mileage = newMileage + (int)existingReminder.ReminderMileageInterval;
}
else
{
existingReminder.Mileage += existingReminder.CustomMileageInterval;
existingReminder.Mileage = newMileage + existingReminder.CustomMileageInterval;
}
}
else if (existingReminder.Metric == ReminderMetric.Odometer)
{
if (existingReminder.ReminderMileageInterval != ReminderMileageInterval.Other)
{
existingReminder.Mileage += (int)existingReminder.ReminderMileageInterval;
existingReminder.Mileage = newMileage + (int)existingReminder.ReminderMileageInterval;
} else
{
existingReminder.Mileage += existingReminder.CustomMileageInterval;
existingReminder.Mileage = newMileage + existingReminder.CustomMileageInterval;
}
}
else if (existingReminder.Metric == ReminderMetric.Date)
{
if (existingReminder.ReminderMonthInterval != ReminderMonthInterval.Other)
{
existingReminder.Date = existingReminder.Date.AddMonths((int)existingReminder.ReminderMonthInterval);
existingReminder.Date = newDate.AddMonths((int)existingReminder.ReminderMonthInterval);
}
else
{
existingReminder.Date = existingReminder.Date.AddMonths(existingReminder.CustomMonthInterval);
existingReminder.Date = newDate.AddMonths(existingReminder.CustomMonthInterval);
}
}
return existingReminder;
@@ -73,7 +75,8 @@ namespace CarCareTracker.Helper
Description = reminder.Description,
Notes = reminder.Notes,
Metric = reminder.Metric,
IsRecurring = reminder.IsRecurring
IsRecurring = reminder.IsRecurring,
Tags = reminder.Tags
};
if (reminder.Metric == ReminderMetric.Both)
{

View File

@@ -25,8 +25,7 @@ namespace CarCareTracker.Helper
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = 0,
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
DistanceTraveled = x.Sum(y=>y.DistanceTraveled)
});
}
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
@@ -39,9 +38,7 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y=>y.Mileage),
MinMileage = x.Min(y=>y.Mileage)
Cost = x.Sum(y => y.Cost)
});
}
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
@@ -54,9 +51,7 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
Cost = x.Sum(y => y.Cost)
});
}
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
@@ -69,9 +64,7 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
Cost = x.Sum(y => y.Cost)
});
}
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
@@ -84,9 +77,7 @@ namespace CarCareTracker.Helper
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost),
MaxMileage = x.Max(y => y.Mileage),
MinMileage = x.Min(y => y.Mileage)
Cost = x.Sum(y => y.Cost)
});
}
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)

View File

@@ -8,13 +8,13 @@ namespace CarCareTracker.Helper
/// </summary>
public static class StaticHelper
{
public static string VersionNumber = "1.2.5";
public static string VersionNumber = "1.3.5";
public static string DbName = "data/cartracker.db";
public static string UserConfigPath = "config/userConfig.json";
public static string GenericErrorMessage = "An error occurred, please try again later";
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
{
switch (input)
@@ -244,5 +244,48 @@ namespace CarCareTracker.Helper
var motd = config["LUBELOGGER_MOTD"] ?? "Not Configured";
Console.WriteLine($"Message Of The Day: {motd}");
}
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
{
if (string.IsNullOrWhiteSpace(webhookURL))
{
return;
}
var httpClient = new HttpClient();
var httpParams = new Dictionary<string, string>
{
{ "vehicleId", vehicleId.ToString() },
{ "username", username },
{ "action", action },
};
httpClient.PostAsJsonAsync(webhookURL, httpParams);
}
public static string GetImportModeIcon(ImportMode importMode)
{
switch (importMode)
{
case ImportMode.ServiceRecord:
return "bi-card-checklist";
case ImportMode.RepairRecord:
return "bi-exclamation-octagon";
case ImportMode.UpgradeRecord:
return "bi-wrench-adjustable";
case ImportMode.TaxRecord:
return "bi-currency-dollar";
case ImportMode.SupplyRecord:
return "bi-shop";
case ImportMode.PlanRecord:
return "bi-bar-chart-steps";
case ImportMode.OdometerRecord:
return "bi-speedometer";
case ImportMode.GasRecord:
return "bi-fuel-pump";
case ImportMode.NoteRecord:
return "bi-journal-bookmark";
case ImportMode.ReminderRecord:
return "bi-bell";
default:
return "bi-file-bar-graph";
}
}
}
}

View File

@@ -1,11 +1,6 @@
LubeLogger by Hargata Softworks is licensed under the MIT License for individual
and personal use. Commercial users and/or corporate entities are required
to maintain an active subscription in order to continue using LubeLogger.
For pricing information please contact us at hargatasoftworks@gmail.com
MIT License
Copyright (c) 2023 Hargata Softworks
Copyright (c) 2024 Hargata Softworks
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

67
Logic/OdometerLogic.cs Normal file
View File

@@ -0,0 +1,67 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
namespace CarCareTracker.Logic
{
public interface IOdometerLogic
{
int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords);
bool AutoInsertOdometerRecord(OdometerRecord odometer);
List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords);
}
public class OdometerLogic: IOdometerLogic
{
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly ILogger<IOdometerLogic> _logger;
public OdometerLogic(IOdometerRecordDataAccess odometerRecordDataAccess, ILogger<IOdometerLogic> logger)
{
_odometerRecordDataAccess = odometerRecordDataAccess;
_logger = logger;
}
public int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords)
{
if (!odometerRecords.Any())
{
odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
}
if (!odometerRecords.Any())
{
//no existing odometer records for this vehicle.
return 0;
}
return odometerRecords.Max(x => x.Mileage);
}
public bool AutoInsertOdometerRecord(OdometerRecord odometer)
{
var lastReportedMileage = GetLastOdometerRecordMileage(odometer.VehicleId, new List<OdometerRecord>());
odometer.InitialMileage = lastReportedMileage != default ? lastReportedMileage : odometer.Mileage;
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometer);
return result;
}
public List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords)
{
//perform ordering
odometerRecords = odometerRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
int previousMileage = 0;
for (int i = 0; i < odometerRecords.Count; i++)
{
var currentObject = odometerRecords[i];
if (previousMileage == default)
{
//first record
currentObject.InitialMileage = currentObject.Mileage;
}
else
{
//subsequent records
currentObject.InitialMileage = previousMileage;
}
//save to db.
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(currentObject);
previousMileage = currentObject.Mileage;
}
return odometerRecords;
}
}
}

107
Logic/VehicleLogic.cs Normal file
View File

@@ -0,0 +1,107 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
namespace CarCareTracker.Logic
{
public interface IVehicleLogic
{
int GetMaxMileage(int vehicleId);
int GetMinMileage(int vehicleId);
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId);
}
public class VehicleLogic: IVehicleLogic
{
private readonly IServiceRecordDataAccess _serviceRecordDataAccess;
private readonly IGasRecordDataAccess _gasRecordDataAccess;
private readonly ICollisionRecordDataAccess _collisionRecordDataAccess;
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
public VehicleLogic(
IServiceRecordDataAccess serviceRecordDataAccess,
IGasRecordDataAccess gasRecordDataAccess,
ICollisionRecordDataAccess collisionRecordDataAccess,
IUpgradeRecordDataAccess upgradeRecordDataAccess,
IOdometerRecordDataAccess odometerRecordDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess,
IReminderHelper reminderHelper
) {
_serviceRecordDataAccess = serviceRecordDataAccess;
_gasRecordDataAccess = gasRecordDataAccess;
_collisionRecordDataAccess = collisionRecordDataAccess;
_upgradeRecordDataAccess = upgradeRecordDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper;
}
public int GetMaxMileage(int vehicleId)
{
var numbersArray = new List<int>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
if (serviceRecords.Any())
{
numbersArray.Add(serviceRecords.Max(x => x.Mileage));
}
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
if (repairRecords.Any())
{
numbersArray.Add(repairRecords.Max(x => x.Mileage));
}
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
if (gasRecords.Any())
{
numbersArray.Add(gasRecords.Max(x => x.Mileage));
}
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
if (upgradeRecords.Any())
{
numbersArray.Add(upgradeRecords.Max(x => x.Mileage));
}
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
if (odometerRecords.Any())
{
numbersArray.Add(odometerRecords.Max(x => x.Mileage));
}
return numbersArray.Any() ? numbersArray.Max() : 0;
}
public int GetMinMileage(int vehicleId)
{
var numbersArray = new List<int>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
if (serviceRecords.Any())
{
numbersArray.Add(serviceRecords.Min(x => x.Mileage));
}
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
if (repairRecords.Any())
{
numbersArray.Add(repairRecords.Min(x => x.Mileage));
}
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
if (gasRecords.Any())
{
numbersArray.Add(gasRecords.Min(x => x.Mileage));
}
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
if (upgradeRecords.Any())
{
numbersArray.Add(upgradeRecords.Min(x => x.Mileage));
}
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
if (odometerRecords.Any())
{
numbersArray.Add(odometerRecords.Min(x => x.Mileage));
}
return numbersArray.Any() ? numbersArray.Min() : 0;
}
public bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId)
{
var currentMileage = GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
return results.Any(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
}
}
}

View File

@@ -10,6 +10,7 @@ namespace CarCareTracker.MapProfile
Map(m => m.Date).Name(["date", "fuelup_date"]);
Map(m => m.DateCreated).Name(["datecreated"]);
Map(m => m.DateModified).Name(["datemodified"]);
Map(m => m.InitialOdometer).Name(["initialodometer"]);
Map(m => m.Odometer).Name(["odometer"]);
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
@@ -26,6 +27,18 @@ namespace CarCareTracker.MapProfile
Map(m => m.Type).Name(["type"]);
Map(m => m.Priority).Name(["priority"]);
Map(m => m.Tags).Name(["tags"]);
Map(m => m.ExtraFields).Convert(row =>
{
var attributes = new Dictionary<string, string>();
foreach (var header in row.Row.HeaderRecord)
{
if (header.ToLower().StartsWith("extrafield_"))
{
attributes.Add(header.Substring(11), row.Row.GetField(header));
}
}
return attributes;
}); ;
}
}
}

View File

@@ -4,7 +4,7 @@
{
public int Id { get; set; }
public int VehicleId { get; set; }
public int ReminderRecordId { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>();
public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int Mileage { get; set; }
public string Description { get; set; }
@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public CollisionRecord ToCollisionRecord() { return new CollisionRecord {
Id = Id,
VehicleId = VehicleId,

View File

@@ -4,7 +4,6 @@
{
public string EmailServer { get; set; }
public string EmailFrom { get; set; }
public bool UseSSL { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class GasRecordEditModel
{
public List<int> RecordIds { get; set; } = new List<int>();
public GasRecord EditRecord { get; set; } = new GasRecord();
}
}

View File

@@ -11,6 +11,8 @@
public string Scope { get; set; }
public string State { get; set; }
public bool ValidateState { get; set; } = false;
public bool DisableRegularLogin { get; set; } = false;
public string LogOutURL { get; set; } = "";
public string RemoteAuthURL { get { return $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}"; } }
}
}

View File

@@ -5,7 +5,9 @@
public int Id { get; set; }
public int VehicleId { get; set; }
public DateTime Date { get; set; }
public int InitialMileage { get; set; }
public int Mileage { get; set; }
public int DistanceTraveled { get { return Mileage - InitialMileage; } }
public string Notes { get; set; }
public List<string> Tags { get; set; } = new List<string>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class OdometerRecordEditModel
{
public List<int> RecordIds { get; set; } = new List<int>();
public OdometerRecord EditRecord { get; set; } = new OdometerRecord();
}
}

View File

@@ -5,11 +5,12 @@
public int Id { get; set; }
public int VehicleId { get; set; }
public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int InitialMileage { get; set; }
public int Mileage { get; set; }
public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields }; }
public OdometerRecord ToOdometerRecord() { return new OdometerRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Mileage = Mileage, Notes = Notes, Files = Files, Tags = Tags, ExtraFields = ExtraFields, InitialMileage = InitialMileage }; }
}
}

View File

@@ -17,6 +17,7 @@
public decimal Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public PlanRecord ToPlanRecord() { return new PlanRecord {
Id = Id,
VehicleId = VehicleId,

View File

@@ -14,5 +14,6 @@
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
public List<string> Tags { get; set; } = new List<string>();
}
}

View File

@@ -14,6 +14,7 @@
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
public ReminderMonthInterval ReminderMonthInterval { get; set; } = ReminderMonthInterval.OneYear;
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;
public List<string> Tags { get; set; } = new List<string>();
public ReminderRecord ToReminderRecord()
{
return new ReminderRecord
@@ -29,7 +30,8 @@
ReminderMonthInterval = ReminderMonthInterval,
CustomMileageInterval = CustomMileageInterval,
CustomMonthInterval = CustomMonthInterval,
Notes = Notes
Notes = Notes,
Tags = Tags
};
}
}

View File

@@ -17,5 +17,6 @@
/// Recurring Reminders
/// </summary>
public bool IsRecurring { get; set; } = false;
public List<string> Tags { get; set; } = new List<string>();
}
}

View File

@@ -5,8 +5,6 @@
public int MonthId { get; set; }
public string MonthName { get; set; }
public decimal Cost { get; set; }
public int MaxMileage { get; set; }
public int MinMileage { get; set; }
public int DistanceTraveled { get; set; }
}
}

View File

@@ -13,5 +13,8 @@
public decimal TotalCostPerMile { get; set; }
public decimal TotalGasCostPerMile { get; set; }
public string DistanceUnit { get; set; }
public decimal TotalDepreciation { get; set; }
public decimal DepreciationPerDay { get; set; }
public decimal DepreciationPerMile { get; set; }
}
}

9
Models/SearchResult.cs Normal file
View File

@@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public class SearchResult
{
public int Id { get; set; }
public ImportMode RecordType { get; set; }
public string Description { get; set; }
}
}

View File

@@ -4,7 +4,7 @@
{
public int Id { get; set; }
public int VehicleId { get; set; }
public int ReminderRecordId { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>();
public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int Mileage { get; set; }
public string Description { get; set; }
@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public ServiceRecord ToServiceRecord() { return new ServiceRecord {
Id = Id,
VehicleId = VehicleId,

View File

@@ -4,5 +4,6 @@ namespace CarCareTracker.Models
{
public UserConfig UserConfig { get; set; }
public List<string> UILanguages { get; set; }
public Sponsors Sponsors { get; set; } = new Sponsors();
}
}

View File

@@ -11,6 +11,7 @@
public string Type { get; set; }
public string Priority { get; set; }
public string Progress { get; set; }
public string InitialOdometer { get; set; }
public string Odometer { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
@@ -24,6 +25,7 @@
public string PartSupplier { get; set; }
public string PartQuantity { get; set; }
public string Tags { get; set; }
public Dictionary<string,string> ExtraFields {get;set;}
}
public class SupplyRecordExportModel
@@ -50,6 +52,7 @@
public class OdometerRecordExportModel
{
public string Date { get; set; }
public string InitialOdometer { get; set; }
public string Odometer { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }

10
Models/Sponsors.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public class Sponsors
{
public List<string> LifeTime { get; set; } = new List<string>();
public List<string> Bronze { get; set; } = new List<string>();
public List<string> Silver { get; set; } = new List<string>();
public List<string> Gold { get; set; } = new List<string>();
}
}

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class SupplyUsageViewModel
{
public List<SupplyRecord> Supplies { get; set; } = new List<SupplyRecord>();
public List<SupplyUsage> Usage { get; set; } = new List<SupplyUsage>();
}
}

View File

@@ -4,7 +4,7 @@
{
public int Id { get; set; }
public int VehicleId { get; set; }
public int ReminderRecordId { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>();
public string Date { get; set; } = DateTime.Now.ToShortDateString();
public string Description { get; set; }
public decimal Cost { get; set; }

View File

@@ -4,7 +4,7 @@
{
public int Id { get; set; }
public int VehicleId { get; set; }
public int ReminderRecordId { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>();
public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int Mileage { get; set; }
public string Description { get; set; }
@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord {
Id = Id,
VehicleId = VehicleId,

View File

@@ -10,9 +10,20 @@
public string LicensePlate { get; set; }
public string PurchaseDate { get; set; }
public string SoldDate { get; set; }
public decimal PurchasePrice { get; set; }
public decimal SoldPrice { get; set; }
public bool IsElectric { get; set; } = false;
public bool UseHours { get; set; } = false;
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<string> Tags { get; set; } = new List<string>();
public bool HasOdometerAdjustment { get; set; } = false;
/// <summary>
/// Primarily used for vehicles with odometer units different from user's settings.
/// </summary>
public string OdometerMultiplier { get; set; } = "1";
/// <summary>
/// Primarily used for vehicles where the odometer does not reflect actual mileage.
/// </summary>
public string OdometerDifference { get; set; } = "0";
}
}

View File

@@ -0,0 +1,19 @@
namespace CarCareTracker.Models
{
public class VehicleViewModel
{
public int Id { get; set; }
public string ImageLocation { get; set; } = "/defaults/noimage.png";
public int Year { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string LicensePlate { get; set; }
public string SoldDate { get; set; }
public bool IsElectric { get; set; } = false;
public bool UseHours { get; set; } = false;
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<string> Tags { get; set; } = new List<string>();
public int LastReportedMileage;
public bool HasReminders = false;
}
}

View File

@@ -16,6 +16,9 @@ StaticHelper.InitMessage(builder.Configuration);
// Add services to the container.
builder.Services.AddControllersWithViews();
//LiteDB is always injected even if user uses Postgres.
builder.Services.AddSingleton<ILiteDBHelper, LiteDBHelper>();
//data access method
if (!string.IsNullOrWhiteSpace(builder.Configuration["POSTGRES_CONNECTION"])){
builder.Services.AddSingleton<IVehicleDataAccess, PGVehicleDataAccess>();
@@ -69,6 +72,8 @@ builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
//configure logic
builder.Services.AddSingleton<ILoginLogic, LoginLogic>();
builder.Services.AddSingleton<IUserLogic, UserLogic>();
builder.Services.AddSingleton<IOdometerLogic, OdometerLogic>();
builder.Services.AddSingleton<IVehicleLogic, VehicleLogic>();
if (!Directory.Exists("data"))
{
@@ -106,7 +111,21 @@ var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
if (ctx.Context.Request.Path.StartsWithSegments("/images") || ctx.Context.Request.Path.StartsWithSegments("/documents"))
{
ctx.Context.Response.Headers.Add("Cache-Control", "no-store");
if (!ctx.Context.User.Identity.IsAuthenticated)
{
ctx.Context.Response.Redirect("/Login");
}
}
}
});
app.UseRouting();

View File

@@ -22,15 +22,6 @@ LubeLogger is available as both a Docker Image and a Windows Standalone Executab
Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started) on how to download either of them
### Docker Setup (Manual Build for Advanced Users)
1. Install Docker
2. Clone this repo
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
4. Run `docker build -t lubelogger -f Dockerfile .`
5. CHECK docker-compose.yml and make sure the mounting directories look correct.
6. If using traefik, use docker-compose.traefik.yml
7. Run `docker-compose up`
### Need Help?
[Documentation](https://docs.lubelogger.com/)
@@ -39,19 +30,18 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started)
[Search Existing Issues](https://github.com/hargata/lubelog/issues)
## Dependencies
- Bootstrap
- LiteDB
- Npgsql
- Bootstrap-DatePicker
- SweetAlert2
- CsvHelper
- Chart.js
- Drawdown
- [Bootstrap](https://github.com/twbs/bootstrap)
- [LiteDB](https://github.com/mbdavid/litedb)
- [Npgsql](https://github.com/npgsql/npgsql)
- [Bootstrap-DatePicker](https://github.com/uxsolutions/bootstrap-datepicker)
- [SweetAlert2](https://github.com/sweetalert2/sweetalert2)
- [CsvHelper](https://github.com/JoshClose/CsvHelper)
- [Chart.js](https://github.com/chartjs/Chart.js)
- [Drawdown](https://github.com/adamvleggett/drawdown)
- [MailKit](https://github.com/jstedfast/MailKit)
## License
LubeLogger utilizes a dual-licensing model, see [License](/LICENSE) for more information
MIT
## Support
Support this project by [Subscribing on Patreon](https://patreon.com/LubeLogger) or [Making a Donation](https://buy.stripe.com/aEU9Egc8DdMc9bO144)
Note: Commercial users are required to maintain an active Patreon subscripton to be compliant with our licensing model.

View File

@@ -16,7 +16,7 @@
<div class="col-1">
<h6>Method</h6>
</div>
<div class="col-5">
<div class="col-5 copyable">
<h6>Endpoint</h6>
</div>
<div class="col-3">
@@ -30,7 +30,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicles</code>
</div>
<div class="col-3">
@@ -44,7 +44,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/servicerecords</code>
</div>
<div class="col-3">
@@ -58,7 +58,7 @@
<div class="col-1">
POST
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/servicerecords/add</code>
</div>
<div class="col-3">
@@ -80,7 +80,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/repairrecords</code>
</div>
<div class="col-3">
@@ -94,7 +94,7 @@
<div class="col-1">
POST
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/repairrecords/add</code>
</div>
<div class="col-3">
@@ -116,7 +116,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/upgraderecords</code>
</div>
<div class="col-3">
@@ -130,7 +130,7 @@
<div class="col-1">
POST
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/upgraderecords/add</code>
</div>
<div class="col-3">
@@ -152,7 +152,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/taxrecords</code>
</div>
<div class="col-3">
@@ -166,7 +166,7 @@
<div class="col-1">
POST
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/taxrecords/add</code>
</div>
<div class="col-3">
@@ -187,7 +187,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/gasrecords</code>
</div>
<div class="col-3">
@@ -205,7 +205,7 @@
<div class="col-1">
POST
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/gasrecords/add</code>
</div>
<div class="col-3">
@@ -229,7 +229,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/reminders</code>
</div>
<div class="col-3">
@@ -245,7 +245,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/reminders/send</code>
</div>
<div class="col-3">
@@ -260,7 +260,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/makebackup</code>
</div>
<div class="col-3">
@@ -275,7 +275,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords</code>
</div>
<div class="col-3">
@@ -289,7 +289,7 @@
<div class="col-1">
GET
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/latest</code>
</div>
<div class="col-3">
@@ -303,7 +303,7 @@
<div class="col-1">
POST
</div>
<div class="col-5">
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/add</code>
</div>
<div class="col-3">
@@ -314,8 +314,14 @@
<br />
Body(form-data): {<br />
date - Date to be entered<br />
initialOdometer - Initial Odometer reading(optional)<br />
odometer - Odometer reading<br />
notes - notes(optional)<br />
}
</div>
</div>
<script>
$('.copyable').on('click', function (e) {
copyToClipboard(e.currentTarget);
})
</script>

View File

@@ -15,11 +15,11 @@
}
@model AdminViewModel
<div class="container">
<div class="row">
<div class="col-1">
<div class="row d-flex align-items-center justify-content-between justify-content-md-start">
<div class="col-2 col-md-1">
<a href="/Home" class="btn btn-secondary btn-md mt-1 mb-1"><i class="bi bi-arrow-left-square"></i></a>
</div>
<div class="col-11">
<div class="col-6 col-md-7 text-end text-md-start">
<span class="display-6">@translator.Translate(userLanguage, "Admin Panel")</span>
</div>
</div>
@@ -120,11 +120,6 @@
}
})
}
function copyToClipboard(e) {
var textToCopy = e.textContent;
navigator.clipboard.writeText(textToCopy);
successToast("Copied to Clipboard");
}
function generateNewToken() {
Swal.fire({
title: 'Generate Token',

View File

@@ -12,8 +12,8 @@
ViewData["Title"] = "LubeLogger";
}
@section Scripts {
<script src="~/js/garage.js"></script>
<script src="~/js/supplyrecord.js"></script>
<script src="~/js/garage.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
@@ -41,7 +41,12 @@
<a class="dropdown-item" href="/Admin"><span class="display-3 ms-2"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</span></a>
</li>
}
@if (!User.IsInRole(nameof(UserData.IsRootUser)))
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<li>
<button class="nav-link" onclick="showRootAccountInformationModal()"><span class="display-3 ms-2"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</span></button>
</li>
} else
{
<li>
<button class="nav-link" onclick="showAccountInformationModal()"><span class="display-3 ms-2"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</span></button>
@@ -90,7 +95,12 @@
<a class="dropdown-item" href="/Admin"><i class="bi bi-people me-2"></i>@translator.Translate(userLanguage,"Admin Panel")</a>
</li>
}
@if (!User.IsInRole(nameof(UserData.IsRootUser)))
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<li>
<button class="dropdown-item" onclick="showRootAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
</li>
} else
{
<li>
<button class="dropdown-item" onclick="showAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
@@ -119,7 +129,7 @@
</div>
<div class="modal fade" id="addVehicleModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="addVehicleModalContent">
</div>
</div>

View File

@@ -7,7 +7,7 @@
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title" id="addVehicleModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
<h5 class="modal-title" id="updateAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
</div>
<div class="modal-body">

View File

@@ -1,7 +1,7 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<Vehicle>
@model List<VehicleViewModel>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
@@ -26,7 +26,7 @@
}
<div class="row">
<div class="row gy-3 align-items-stretch vehiclesContainer">
@foreach (Vehicle vehicle in Model)
@foreach (VehicleViewModel vehicle in Model)
{
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
{
@@ -36,6 +36,22 @@
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
{
<div class="vehicle-sold-banner"><p class='display-6 mb-0'>@translator.Translate(userLanguage, "SOLD")</p></div>
} else if (vehicle.LastReportedMileage != default)
{
<div class="vehicle-sold-banner">
<div class="d-flex justify-content-between">
<div>
<span class="ms-2"><i class="bi bi-speedometer me-2"></i>@vehicle.LastReportedMileage.ToString("N0")</span>
</div>
@if (vehicle.HasReminders)
{
<div>
<span class="me-2"><i class="bi bi bi-bell-fill text-warning"></i></span>
</div>
}
</div>
<span></span>
</div>
}
<div class="card-body">
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>

View File

@@ -0,0 +1,26 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model UserData
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title" id="updateRootAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form class="form-inline">
<div class="form-group">
<label for="inputUsername">@translator.Translate(userLanguage, "Username")</label>
<input type="text" id="inputUsername" class="form-control" placeholder="@translator.Translate(userLanguage, "Account Username")" value="@Model.UserName">
<label for="inputPassword">@translator.Translate(userLanguage, "Password")</label>
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "Password")" value="">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideAccountInformationModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" onclick="validateAndSaveRootUserAccount()" class="btn btn-primary">@translator.Translate(userLanguage, "Update")</button>
</div>

View File

@@ -219,7 +219,7 @@
</p>
<p class="lead">
If you enjoyed using this app, please consider spreading the good word.<br />
If you are a commercial user, or if you just want to support the development of this project, consider subscribing to <a class="link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" href="https://www.patreon.com/LubeLogger" target="_blank">our Patreon</a> or make a <a class="link-light link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" href="https://buy.stripe.com/aEU9Egc8DdMc9bO144" target="_blank">donation</a>
If you want to support the development of this project, consider subscribing to <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" href="https://www.patreon.com/LubeLogger" target="_blank">our Patreon</a> or make a <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover" href="https://buy.stripe.com/aEU9Egc8DdMc9bO144" target="_blank">donation</a>
</p>
<div class="d-flex justify-content-center">
<h6 class="display-7 mt-2">Hometown Shoutout</h6>
@@ -247,9 +247,11 @@
<li class="list-group-item">CsvHelper</li>
<li class="list-group-item">Chart.js</li>
<li class="list-group-item">Drawdown</li>
<li class="list-group-item">MailKit</li>
</ul>
</div>
</div>
@await Html.PartialAsync("_Sponsors", Model.Sponsors)
<div class="modal fade" data-bs-focus="false" id="extraFieldModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="extraFieldModalContent">
@@ -402,6 +404,7 @@
function restoreBackup(event) {
let formData = new FormData();
formData.append("file", event.files[0]);
console.log('LubeLogger - DB Restoration Started');
sloader.show();
$.ajax({
url: "/Files/HandleFileUpload",
@@ -415,16 +418,21 @@
$.post('/Files/RestoreBackup', { fileName: response }, function (data) {
sloader.hide();
if (data) {
console.log('LubeLogger - DB Restoration Completed');
successToast("Backup Restored");
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
} else {
errorToast(genericErrorMessage());
console.log('LubeLogger - DB Restoration Failed - Failed to process backup file.');
}
});
} else {
console.log('LubeLogger - DB Restoration Failed - Failed to upload backup file.');
}
},
error: function () {
sloader.hide();
console.log('LubeLogger - DB Restoration Failed - Request failed to reach backend, please check file size.');
errorToast("An error has occurred, please check the file size and try again later.");
}
});

View File

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

View File

@@ -9,7 +9,7 @@
ViewData["Title"] = "LubeLogger - Login";
}
@section Scripts {
<script src="~/js/login.js"></script>
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">

View File

@@ -11,7 +11,7 @@
ViewData["Title"] = "LubeLogger - Login";
}
@section Scripts {
<script src="~/js/login.js"></script>
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">

View File

@@ -10,7 +10,7 @@
ViewData["Title"] = "LubeLogger - Register";
}
@section Scripts {
<script src="~/js/login.js"></script>
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">

View File

@@ -9,7 +9,7 @@
ViewData["Title"] = "LubeLogger - Register";
}
@section Scripts {
<script src="~/js/login.js"></script>
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">

View File

@@ -9,7 +9,7 @@
ViewData["Title"] = "LubeLogger - Register";
}
@section Scripts {
<script src="~/js/login.js"></script>
<script src="~/js/login.js?v=@StaticHelper.VersionNumber"></script>
}
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
<div class="row">

View File

@@ -45,12 +45,12 @@
<link rel="apple-touch-startup-image" href="~/defaults/lubelogger_launch.png" />
<link rel="manifest" href="~/manifest.json">
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/js/shared.js"></script>
<script src="~/js/shared.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
<script src="~/lib/bootstrap-tagsinput/bootstrap-tagsinput.js"></script>
<script src="~/sweetalert/sweetalert2.all.min.js"></script>
<script src="~/js/loader.js"></script>
<script src="~/js/loader.js?v=@StaticHelper.VersionNumber"></script>
<script>
function getGlobalConfig() {
return {
@@ -70,8 +70,8 @@
}
function globalParseFloat(input){
//remove thousands separator.
var thousandSeparator = "@numberFormat.NumberGroupSeparator";
var decimalSeparator = "@numberFormat.NumberDecimalSeparator";
var thousandSeparator = decodeHTMLEntities("@numberFormat.NumberGroupSeparator");
var decimalSeparator = decodeHTMLEntities("@numberFormat.NumberDecimalSeparator");
var currencySymbol = decodeHTMLEntities("@numberFormat.CurrencySymbol");
if (input == "---") {
input = "0";
@@ -85,13 +85,32 @@
return parseFloat(input);
}
function globalFloatToString(input) {
var decimalSeparator = "@numberFormat.NumberDecimalSeparator";
var decimalSeparator = decodeHTMLEntities("@numberFormat.NumberDecimalSeparator");
input = input.replace(".", decimalSeparator);
return input;
}
function genericErrorMessage(){
return decodeHTMLEntities('@translator.Translate(userLanguage, "An error has occurred, please try again later")');
}
function globalAppendCurrency(input){
//check currency symbol position
var currencySymbolPosition = "@numberFormat.CurrencyPositivePattern";
var currencySymbol = decodeHTMLEntities("@numberFormat.CurrencySymbol");
switch (currencySymbolPosition) {
case "0":
return `${currencySymbol}${input}`;
break;
case "1":
return `${input}${currencySymbol}`;
break;
case "2":
return `${currencySymbol} ${input}`;
break;
case "3":
return `${input} ${currencySymbol}`;
break;
}
}
</script>
@await RenderSectionAsync("Scripts", required: false)
</head>

View File

@@ -10,18 +10,18 @@
}
@model Vehicle
@section Scripts {
<script src="~/js/vehicle.js"></script>
<script src="~/js/servicerecord.js"></script>
<script src="~/js/gasrecord.js"></script>
<script src="~/js/collisionrecord.js"></script>
<script src="~/js/taxrecord.js"></script>
<script src="~/js/reminderrecord.js"></script>
<script src="~/js/upgraderecord.js"></script>
<script src="~/js/note.js"></script>
<script src="~/js/reports.js"></script>
<script src="~/js/supplyrecord.js"></script>
<script src="~/js/planrecord.js"></script>
<script src="~/js/odometerrecord.js"></script>
<script src="~/js/vehicle.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/servicerecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/gasrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/collisionrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/taxrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/reminderrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/upgraderecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/note.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/reports.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/planrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/odometerrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
@@ -139,7 +139,7 @@
</div>
</div>
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="editVehicleModalContent">
</div>
</div>
@@ -169,7 +169,12 @@
</div>
<script>
function GetVehicleId() {
return { vehicleId: @Model.Id};
return {
vehicleId: @Model.Id,
hasOdometerAdjustment: @Model.HasOdometerAdjustment.ToString().ToLower(),
odometerDifference: decodeHTMLEntities('@Model.OdometerDifference'),
odometerMultiplier: decodeHTMLEntities('@Model.OdometerMultiplier')
};
}
function GetDefaultTab() {
return { tab: "@userConfig.DefaultTab" };

View File

@@ -8,7 +8,7 @@
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Repair Record") : translator.Translate(userLanguage,"Edit Repair Record"))</h5>
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Repair Record") : translator.Translate(userLanguage, "Edit Repair Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditCollisionRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddCollisionRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -17,14 +17,22 @@
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="collisionRecordDate">@translator.Translate(userLanguage,"Date")</label>
<label for="collisionRecordDate">@translator.Translate(userLanguage, "Date")</label>
<div class="input-group">
<input type="text" id="collisionRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date repair was performed")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="collisionRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
<label for="collisionRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="collisionRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when repaired")" value="@(isNew ? "" : Model.Mileage)">
<label for="collisionRecordDescription">@translator.Translate(userLanguage,"Description")</label>
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('collisionRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="collisionRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) repaired(i.e. Alternator)")" value="@Model.Description">
@if (isNew)
{
@@ -34,13 +42,13 @@
</div>
</div>
}
<label for="collisionRecordCost">@translator.Translate(userLanguage,"Cost")</label>
<label for="collisionRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
@if (isNew)
{
@await Html.PartialAsync("_SupplyStore", "RepairRecord")
}
<label for="collisionRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<label for="collisionRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="collisionRecordTag">
@foreach (string tag in Model.Tags)
{
@@ -57,15 +65,15 @@
}
</div>
<div class="col-md-6 col-12">
<label for="collisionRecordNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<label for="collisionRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="collisionRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload more documents")</label>
<label for="collisionRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="collisionRecordFiles">
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
</div>
}
else
@@ -75,13 +83,15 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
<label class="form-check-label" for="addReminderCheck">
@translator.Translate(userLanguage,"Add Reminder")
@translator.Translate(userLanguage, "Add Reminder")
</label>
</div>
}
<label for="collisionRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
<label for="collisionRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="collisionRecordFiles">
<br /><small class="text-body-secondary">@translator.Translate(userLanguage,"Max File Size: 28.6MB")</small>
<br />
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
}
</div>
</div>
@@ -96,32 +106,33 @@
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<div class="btn-group" style="margin-right:auto;">
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteCollisionRecord(@Model.Id)">@translator.Translate(userLanguage,"Delete")</button>
<button type="button" class="btn btn-md mt-1 mb-1 btn-danger" onclick="deleteCollisionRecord(@Model.Id)">@translator.Translate(userLanguage, "Delete")</button>
<button type="button" class="btn btn-md btn-danger btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><h6 class="dropdown-header">@translator.Translate(userLanguage,"Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'ServiceRecord')">@translator.Translate(userLanguage,"Service Records")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'UpgradeRecord')">@translator.Translate(userLanguage,"Upgrades")</a></li>
<li><h6 class="dropdown-header">@translator.Translate(userLanguage, "Move To")</h6></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'ServiceRecord')">@translator.Translate(userLanguage, "Service Records")</a></li>
<li><a class="dropdown-item" href="#" onclick="moveRecord(@Model.Id, 'RepairRecord', 'UpgradeRecord')">@translator.Translate(userLanguage, "Upgrades")</a></li>
</ul>
</div>
}
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">@translator.Translate(userLanguage,"Cancel")</button>
<button type="button" class="btn btn-secondary" onclick="hideAddCollisionRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle()">@translator.Translate(userLanguage,"Add New Repair Record")</button>
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle()">@translator.Translate(userLanguage, "Add New Repair Record")</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">@translator.Translate(userLanguage,"Edit Repair Record")</button>
<button type="button" class="btn btn-primary" onclick="saveCollisionRecordToVehicle(true)">@translator.Translate(userLanguage, "Edit Repair Record")</button>
}
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
<script>
var uploadedFiles = [];
var selectedSupplies = [];
var recurringReminderRecordId = 0;
var copySuppliesAttachments = false;
var recurringReminderRecordId = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)

View File

@@ -108,13 +108,13 @@
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1 flex-grow-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-3 flex-grow-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('accident-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
@@ -123,13 +123,25 @@
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@collisionRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditCollisionRecordModal,@collisionRecord.Id)" data-tags='@string.Join(" ", collisionRecord.Tags)'>
<td class="col-2 col-xl-1 flex-grow-1" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(collisionRecord.Date)">@collisionRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1" data-column="odometer">@collisionRecord.Mileage</td>
<td class="col-3 col-xl-4 flex-grow-1" data-column="description">@collisionRecord.Description</td>
<td class="col-2 flex-grow-1" data-column="cost" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
<td class="col-3 flex-grow-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@collisionRecord.Mileage</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@collisionRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(collisionRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = collisionRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
@@ -159,6 +171,8 @@
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -172,16 +172,16 @@
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 flex-grow-1" data-column="daterefueled">@translator.Translate(userLanguage, "Date Refueled")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="odometer">@($"{translator.Translate(userLanguage, "Odometer")}({distanceUnit})")</th>
<th scope="col" class="col-1 flex-grow-1" data-column="delta" style="cursor:pointer;" onclick="toggleSort('gas-tab-pane', this)">@($"Δ({distanceUnit})")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="consumption" data-gas="consumption" data-unit="@consumptionUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{translator.Translate(userLanguage, "Consumption")}({consumptionUnit})")</th>
<th scope="col" class="col-3 flex-grow-1" 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" 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" data-column="unitcost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Unit Cost")</th>
<th scope="col" class="col-3 flex-grow-1" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@($"{translator.Translate(userLanguage, "Odometer")}({distanceUnit})")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1" data-column="delta" style="cursor:pointer;" onclick="toggleSort('gas-tab-pane', this)">@($"Δ({distanceUnit})")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="consumption" data-gas="consumption" data-unit="@consumptionUnit" onclick="toggleSort('gas-tab-pane', this)" oncontextmenu="toggleUnits(this)" style="cursor:pointer;">@($"{translator.Translate(userLanguage, "Consumption")}({consumptionUnit})")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" 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" 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" data-column="unitcost" onclick="toggleSort('gas-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Unit Cost")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" style='display:none;' data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
@@ -190,16 +190,28 @@
{
<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" data-column="daterefueled">@gasRecord.Date</td>
<td class="col-2 flex-grow-1" data-column="odometer" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@gasRecord.Mileage</td>
<td class="col-1 flex-grow-1" data-column="delta">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
<td class="col-2 flex-grow-1" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString("F")</td>
<td class="col-3 flex-grow-1" 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" data-column="cost" data-record-type="cost">@((hideZero && gasRecord.Cost == default) ? "---" : gasRecord.Cost.ToString(gasCostFormat))</td>
<td class="col-1 flex-grow-1" data-column="unitcost" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
<td class="col-3 flex-grow-1 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer" data-gas-type="mileage" data-gas-aggregate="@gasRecord.DeltaMileage" data-gas-original="@gasRecord.Mileage">@gasRecord.Mileage</td>
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="delta">@(gasRecord.DeltaMileage == default ? "---" : gasRecord.DeltaMileage)</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="consumption" data-gas-type="consumption" data-gas-aggregate="@gasRecord.Gallons">@gasRecord.Gallons.ToString("F")</td>
<td class="col-3 flex-grow-1 flex-shrink-1" 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" 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" data-column="unitcost" data-gas-type="unitcost">@((hideZero && gasRecord.CostPerGallon == default) ? "---" : gasRecord.CostPerGallon.ToString(gasCostFormat))</td>
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="notes">@StaticHelper.TruncateStrings(gasRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(gasRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = gasRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
@@ -219,9 +231,12 @@
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -17,27 +17,27 @@
consumptionUnit = "kWh";
} else if (useUKMPG)
{
consumptionUnit = "liters";
consumptionUnit = @translator.Translate(userLanguage, "liters");
}
else
{
consumptionUnit = useMPG ? "gallons" : "liters";
consumptionUnit = useMPG ? @translator.Translate(userLanguage, "gallons") : @translator.Translate(userLanguage, "liters");
}
if (useHours)
{
distanceUnit = "hours";
distanceUnit = @translator.Translate(userLanguage, "hours");
}
else if (useUKMPG)
{
distanceUnit = "miles";
distanceUnit = @translator.Translate(userLanguage, "miles");
}
else
{
distanceUnit = useMPG ? "miles" : "kilometers";
distanceUnit = useMPG ? @translator.Translate(userLanguage, "miles") : @translator.Translate(userLanguage, "kilometers");
}
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Gas Record") : translator.Translate(userLanguage,"Edit Gas Record"))</h5>
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Gas Record") : translator.Translate(userLanguage, "Edit Gas Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditGasRecordModal({Model.GasRecord.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddGasRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -52,7 +52,15 @@
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="gasRecordMileage">@($"{translator.Translate(userLanguage,"Odometer Reading")}({distanceUnit})")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when refueled")" value="@(isNew ? "" : Model.GasRecord.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('gasRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="gasRecordGallons">@($"{translator.Translate(userLanguage, "Fuel Consumption")}({consumptionUnit})")</label>
<input type="text" inputmode="decimal" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
<div class="form-check form-switch">

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Note") : translator.Translate(userLanguage, "Edit Note"))</h5>
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Note") : translator.Translate(userLanguage, "Edit Note"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditNoteModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddNoteModal()" aria-label="Close"></button>
</div>
<div class="modal-body">

View File

@@ -8,7 +8,7 @@
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Odometer Record") : translator.Translate(userLanguage,"Edit Odometer Record"))</h5>
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Odometer Record") : translator.Translate(userLanguage, "Edit Odometer Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditOdometerRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddOdometerRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -22,8 +22,26 @@
<input type="text" id="odometerRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date recorded")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="initialOdometerRecordMileage">@translator.Translate(userLanguage, "Initial Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="initialOdometerRecordMileage" @(Model.InitialMileage != default ? "disabled" : "") class="form-control" placeholder="@translator.Translate(userLanguage,"Initial Odometer reading")" value="@(Model.InitialMileage)">
@if (Model.InitialMileage != default)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-secondary zero-y-padding" onclick="toggleInitialOdometerEnabled()"><i class="bi bi-pencil"></i></button>
</div>
}
</div>
<label for="odometerRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="odometerRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading")" value="@(isNew ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('odometerRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="odometerRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
<select multiple class="form-select" id="odometerRecordTag">
@foreach (string tag in Model.Tags)

View File

@@ -19,6 +19,7 @@
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Odometer Records")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-primary" data-aggregate-type="sum-distance">@($"{translator.Translate(userLanguage, "Total Distance")}: {Model.Sum(x => x.DistanceTraveled)}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterTable('odometer-tab-pane', this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
@@ -52,12 +53,24 @@
<label class="form-check-label stretched-link" for="chkCol_Date">@translator.Translate(userLanguage, "Date")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='initialodometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_InitialOdometer" checked>
<label class="form-check-label stretched-link" for="chkCol_InitialOdometer">@translator.Translate(userLanguage, "Initial Odometer")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='odometer' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Odometer" checked>
<label class="form-check-label stretched-link" for="chkCol_Odometer">@translator.Translate(userLanguage, "Odometer")</label>
</div>
</li>
<li class="dropdown-item">
<div class="list-group-item">
<input class="form-check-input col-visible-toggle" data-column-toggle='distance' onChange="showTableColumns(this, 'OdometerRecord')" type="checkbox" id="chkCol_Distance" checked>
<label class="form-check-label stretched-link" for="chkCol_Distance">@translator.Translate(userLanguage, "Distance")</label>
</div>
</li>
<li class="dropdown-item">
<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>
@@ -95,11 +108,13 @@
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1 flex-grow-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-3 flex-grow-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-7 col-xl-8 flex-grow-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="initialodometer">@translator.Translate(userLanguage, "Initial Odometer")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="distance" onclick="toggleSort('odometer-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Distance")</th>
<th scope="col" class="col-2 col-xl-3 flex-grow-1 flex-shrink-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
@@ -108,11 +123,25 @@
{
<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" data-column="date">@odometerRecord.Date.ToShortDateString()</td>
<td class="col-3 flex-grow-1" data-column="odometer" data-record-type="cost">@odometerRecord.Mileage</td>
<td class="col-7 col-xl-8 flex-grow-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
<td class="col-3 flex-grow-1 flex-shrink-1" data-column="initialodometer">@odometerRecord.InitialMileage</td>
<td class="col-3 flex-grow-1 flex-shrink-1" data-column="odometer">@odometerRecord.Mileage</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="distance" data-record-type="distance">@(odometerRecord.DistanceTraveled == default ? "---" : odometerRecord.DistanceTraveled)</td>
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(odometerRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = odometerRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
@@ -132,9 +161,14 @@
<ul class="table-context-menu dropdown-menu" style="display:none;">
<li><a class="context-menu-multiple context-menu-select-all dropdown-item" href="#" onclick="selectAllRows()">@translator.Translate(userLanguage, "Select All")</a></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleOdometerRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
<li><hr class="context-menu-multiple context-menu-deselect-all dropdown-divider"></li>
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="recalculateDistance()">@translator.Translate(userLanguage, "Recalculate Distance")</a></li>
<li><hr class="context-menu-multiple dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
</ul>
@if (userColumnPreferences.Any())
{

View File

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

View File

@@ -1,5 +1,5 @@
@model PlanRecord
<div class="taskCard @(Model.Progress == PlanProgress.Done ? "nodrag" : "") text-dark user-select-none mt-2 mb-2" draggable="@(Model.Progress == PlanProgress.Done ? "false" : "true")" ondragstart="dragStart(event, @Model.Id)" onclick="@(Model.Progress == PlanProgress.Done ? $"deletePlanRecord({Model.Id})" : $"showEditPlanRecordModal({Model.Id})")">
<div class="taskCard @(Model.Progress == PlanProgress.Done ? "nodrag" : "") text-dark user-select-none mt-2 mb-2" draggable="@(Model.Progress == PlanProgress.Done ? "false" : "true")" ondragstart="dragStart(event, @Model.Id)" onclick="@(Model.Progress == PlanProgress.Done ? $"deletePlanRecord({Model.Id}, true)" : $"showEditPlanRecordModal({Model.Id})")">
<div class="card-body">
<div class="row">
<div class="col-12 col-lg-8 text-truncate">

View File

@@ -8,7 +8,7 @@
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Plan Record") : translator.Translate(userLanguage, "Edit Plan Record"))</h5>
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Plan Record") : translator.Translate(userLanguage, "Edit Plan Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditPlanRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddPlanRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -101,10 +101,6 @@
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="savePlanRecordTemplate()">@translator.Translate(userLanguage, "Save as Template")</a></li>
@if (!Model.CreatedFromReminder)
{
<li><a class="dropdown-item" href="#" onclick="showPlanRecordTemplatesModal()">@translator.Translate(userLanguage, "View Templates")</a></li>
}
</ul>
</div>
}
@@ -117,6 +113,7 @@
<script>
var uploadedFiles = [];
var selectedSupplies = [];
var copySuppliesAttachments = false;
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
@@ -129,7 +126,8 @@
id: @Model.Id,
dateCreated: decodeHTMLEntities('@(Model.DateCreated)'),
reminderRecordId: decodeHTMLEntities('@Model.ReminderRecordId'),
createdFromReminder: @Model.CreatedFromReminder.ToString().ToLower()
createdFromReminder: @Model.CreatedFromReminder.ToString().ToLower(),
isTemplate: false
}
}
</script>

View File

@@ -0,0 +1,117 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model PlanRecordInput
@{
var isNew = Model.Id == 0;
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@translator.Translate(userLanguage, "Edit Plan Record Template")<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditPlanRecordTemplateModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddPlanRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<div class="row">
<div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="planRecordDescription">@translator.Translate(userLanguage, "Description")</label>
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
<input type="text" inputmode="decimal" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
@await Html.PartialAsync("_SupplyStore", "PlanRecordTemplate")
<label for="planRecordType">@translator.Translate(userLanguage, "Type")</label>
<select class="form-select" id="planRecordType">
<!option value="ServiceRecord" @(Model.ImportMode == ImportMode.ServiceRecord || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Service")</!option>
<!option value="RepairRecord" @(Model.ImportMode == ImportMode.RepairRecord ? "selected" : "")>@translator.Translate(userLanguage, "Repair")</!option>
<!option value="UpgradeRecord" @(Model.ImportMode == ImportMode.UpgradeRecord ? "selected" : "")>@translator.Translate(userLanguage, "Upgrade")</!option>
</select>
<label for="planRecordPriority">@translator.Translate(userLanguage, "Priority")</label>
<select class="form-select" id="planRecordPriority">
<!option value="Critical" @(Model.Priority == PlanPriority.Critical ? "selected" : "")>@translator.Translate(userLanguage, "Critical")</!option>
<!option value="Normal" @(Model.Priority == PlanPriority.Normal || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Normal")</!option>
<!option value="Low" @(Model.Priority == PlanPriority.Low ? "selected" : "")>@translator.Translate(userLanguage, "Low")</!option>
</select>
<label for="planRecordProgress">@translator.Translate(userLanguage, "Current Stage")</label>
<select class="form-select" id="planRecordProgress">
<!option value = "Backlog" @(Model.Progress == PlanProgress.Backlog || isNew ? "selected" : "")>@translator.Translate(userLanguage, "Planned")</!option>
<!option value="InProgress" @(Model.Progress == PlanProgress.InProgress ? "selected" : "")>@translator.Translate(userLanguage, "Doing")</!option>
<!option value = "Testing" @(Model.Progress == PlanProgress.Testing ? "selected" : "")>@translator.Translate(userLanguage, "Testing")</!option>
</select>
@foreach (ExtraField field in Model.ExtraFields)
{
var elementId = Guid.NewGuid();
<div class="extra-field">
<label for="@elementId">@field.Name</label>
<input type="text" id="@elementId" class="form-control @(field.IsRequired ? "extra-field-required" : "")" placeholder="@field.Name" value="@field.Value">
</div>
}
</div>
<div class="col-md-6 col-12">
<label for="planRecordNotes">@translator.Translate(userLanguage, "Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
<textarea id="planRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload more documents")</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="planRecordFiles">
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
</div>
}
else
{
<label for="planRecordFiles">@translator.Translate(userLanguage, "Upload documents(optional)")</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="planRecordFiles">
<br />
<small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
}
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
@if (Model.RequisitionHistory.Any())
{
<button type="button" class="btn btn-warning" onclick="toggleSupplyUsageHistory()"><i class="bi bi-shop"></i></button>
}
<button type="button" class="btn btn-danger" onclick="deletePlannerRecordTemplate(@Model.Id)" style="margin-right:auto;">@translator.Translate(userLanguage, "Delete")</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddPlanRecordModal()">@translator.Translate(userLanguage, "Cancel")</button>
<button type="button" class="btn btn-primary" onclick="savePlanRecordTemplate(true)">@translator.Translate(userLanguage, "Edit Plan Record Template")</button>
</div>
@await Html.PartialAsync("_SupplyRequisitionHistory", Model.RequisitionHistory)
<script>
var uploadedFiles = [];
var selectedSupplies = [];
var copySuppliesAttachments = @Model.CopySuppliesAttachment.ToString().ToLower();
getUploadedFilesFromModel();
getSelectedSuppliesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
function getSelectedSuppliesFromModel() {
@foreach(SupplyUsage supplyUsage in Model.Supplies)
{
@:selectedSupplies.push({supplyId: @supplyUsage.SupplyId, quantity: @supplyUsage.Quantity})
}
}
function getPlanRecordModelData() {
return {
id: @Model.Id,
dateCreated: decodeHTMLEntities('@(Model.DateCreated)'),
reminderRecordId: decodeHTMLEntities('@Model.ReminderRecordId'),
createdFromReminder: @Model.CreatedFromReminder.ToString().ToLower(),
isTemplate: true
}
}
</script>

View File

@@ -20,7 +20,7 @@
<tr class="d-flex">
<th scope="col" class="col-8">@translator.Translate(userLanguage,"Description")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Use")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Edit")</th>
</tr>
</thead>
<tbody>
@@ -67,7 +67,7 @@
}
</td>
<td class="col-2"><button type="button" class="btn btn-primary" onclick="usePlannerRecordTemplate(@planRecordTemplate.Id)"><i class="bi bi-plus-square"></i></button></td>
<td class="col-2"><button type="button" class="btn btn-danger" onclick="deletePlannerRecordTemplate(@planRecordTemplate.Id)"><i class="bi bi-trash"></i></button></td>
<td class="col-2"><button type="button" class="btn btn-warning" onclick="showEditPlanRecordTemplateModal(@planRecordTemplate.Id)"><i class="bi bi-pencil-square"></i></button></td>
</tr>
}
</tbody>

View File

@@ -28,6 +28,8 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('PlanRecord')">@translator.Translate(userLanguage,"Import via CSV")</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('PlanRecord')">@translator.Translate(userLanguage,"Export to CSV")</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="showPlanRecordTemplatesModal()">@translator.Translate(userLanguage, "View Templates")</a></li>
</ul>
</div>
}
@@ -102,7 +104,7 @@
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="planRecordTemplateModal" tabindex="-1" role="dialog" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal fade" data-bs-focus="false" id="planRecordTemplateModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="planRecordTemplateModalContent">
</div>

View File

@@ -1,4 +1,18 @@
@model List<ReminderRecord>
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<ReminderRecord>
@if (Model.Count() > 1)
{
<div class="mb-2">
<input class="form-check-input" type="checkbox" onchange="showMultipleRemindersSelector()" id="multipleRemindersCheck">
<label class="form-check-label" for="multipleRemindersCheck">@translator.Translate(userLanguage, "Multiple")</label>
</div>
}
<select class="form-select" id="recurringReminderInput">
@if (Model.Any())
{
@@ -11,3 +25,14 @@
<!option value="0">No Recurring Reminders Found</!option>
}
</select>
<div id="recurringMultipleReminders" style="display:none;">
<ul class="list-group">
@foreach (ReminderRecord reminderRecord in Model)
{
<li class="list-group-item text-start">
<input class="form-check-input" type="checkbox" value="@reminderRecord.Id" id="recurringReminder_@reminderRecord.Id">
<label class="form-check-label stretched-link" for="recurringReminder_@reminderRecord.Id">@reminderRecord.Description</label>
</li>
}
</ul>
</div>

View File

@@ -21,7 +21,7 @@
<input type="text" id="reminderDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Reminder Description")" value="@Model.Description">
<label>@translator.Translate(userLanguage,"Remind me on")</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricDate" value="@(ReminderMetric.Date)" checked="@(Model.Metric == ReminderMetric.Date)">
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricDate" value="@(ReminderMetric.Date)" checked="@(Model.Metric == ReminderMetric.Date)">
<label class="form-check-label" for="reminderMetricDate">@translator.Translate(userLanguage,"Date")</label>
</div>
<div class="input-group">
@@ -29,19 +29,30 @@
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricOdometer" value="@(ReminderMetric.Odometer)" checked="@(Model.Metric == ReminderMetric.Odometer)">
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricOdometer" value="@(ReminderMetric.Odometer)" checked="@(Model.Metric == ReminderMetric.Odometer)">
<label class="form-check-label" for="reminderMetricOdometer">@translator.Translate(userLanguage,"Odometer")</label>
</div>
<div class="input-group">
<input type="number" inputmode="numeric" id="reminderMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Future Odometer Reading")" value="@(isNew ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="appendMileageToOdometer(500)">+500</button>
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('reminderMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="reminderMetricOptions" id="reminderMetricBoth" value="@(ReminderMetric.Both)" checked="@(Model.Metric == ReminderMetric.Both)">
<input class="form-check-input" onclick="enableRecurring()" type="radio" name="reminderMetricOptions" id="reminderMetricBoth" value="@(ReminderMetric.Both)" checked="@(Model.Metric == ReminderMetric.Both)">
<label class="form-check-label" for="reminderMetricBoth">@translator.Translate(userLanguage,"Whichever comes first")</label>
</div>
<div class="d-grid"></div>
<label for="reminderRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
<select multiple class="form-select" id="reminderRecordTag">
@foreach (string tag in Model.Tags)
{
<!option value="@tag">@tag</!option>
}
</select>
</div>
<div class="col-md-6 col-12">
<label for="reminderNotes">@translator.Translate(userLanguage,"Notes(optional)")<a class="link-underline link-underline-opacity-0" onclick="showLinks(this)"><i class="bi bi-markdown ms-2"></i></a></label>
@@ -51,7 +62,7 @@
<label class="form-check-label" for="reminderIsRecurring">@translator.Translate(userLanguage,"Is Recurring")</label>
</div>
<label for="reminderRecurringMileage">@translator.Translate(userLanguage,"Odometer")</label>
<select class="form-select" onchange="checkCustomMileageInterval()" id="reminderRecurringMileage" @(Model.IsRecurring ? "" : "disabled")>
<select class="form-select" onchange="checkCustomMileageInterval()" id="reminderRecurringMileage" @(Model.IsRecurring && (Model.Metric == ReminderMetric.Odometer || Model.Metric == ReminderMetric.Both) ? "" : "disabled")>
<!option value="Other" @(Model.ReminderMileageInterval == ReminderMileageInterval.Other ? "selected" : "")>@(Model.ReminderMileageInterval == ReminderMileageInterval.Other && Model.CustomMileageInterval > 0 ? $"{translator.Translate(userLanguage, "Other")}: {Model.CustomMileageInterval}" : $"{translator.Translate(userLanguage, "Other")}") </!option>
<!option value="FiftyMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyMiles ? "selected" : "")>50 mi. / Km</!option>
<!option value="OneHundredMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredMiles ? "selected" : "")>100 mi. / Km</!option>
@@ -72,7 +83,7 @@
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
</select>
<label for="reminderRecurringMonth">Month</label>
<select class="form-select" onchange="checkCustomMonthInterval()" id="reminderRecurringMonth" @(Model.IsRecurring ? "" : "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="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>

View File

@@ -6,15 +6,26 @@
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
var hasRefresh = Model.Where(x => (x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue) && x.IsRecurring).Any();
var recordTags = Model.SelectMany(x => x.Tags).Distinct();
}
<div class="row">
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center flex-wrap">
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage, "# of Reminders")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-secondary">@($"{translator.Translate(userLanguage, "Past Due")}: {Model.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()}")</span>
<span class="ms-2 badge bg-danger">@($"{translator.Translate(userLanguage, "Very Urgent")}: {Model.Where(x=>x.Urgency == ReminderUrgency.VeryUrgent).Count()}")</span>
<span class="ms-2 badge bg-warning">@($"{translator.Translate(userLanguage, "Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.Urgent).Count()}")</span>
<span class="ms-2 badge bg-success">@($"{translator.Translate(userLanguage, "Not Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count()}")</span>
<span class="ms-2 badge bg-success" data-aggregate-type="count">@($"{translator.Translate(userLanguage, "# of Reminders")}: {Model.Count()}")</span>
<span class="ms-2 badge bg-secondary" data-aggregate-type="pastdue-count">@($"{translator.Translate(userLanguage, "Past Due")}: {Model.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()}")</span>
<span class="ms-2 badge bg-danger" data-aggregate-type="veryurgent-count">@($"{translator.Translate(userLanguage, "Very Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count()}")</span>
<span class="ms-2 badge text-bg-warning" data-aggregate-type="urgent-count">@($"{translator.Translate(userLanguage, "Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.Urgent).Count()}")</span>
<span class="ms-2 badge bg-success" data-aggregate-type="noturgent-count">@($"{translator.Translate(userLanguage, "Not Urgent")}: {Model.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count()}")</span>
@foreach (string recordTag in recordTags)
{
<span onclick="filterReminderTable(this)" class="user-select-none ms-2 rounded-pill badge bg-secondary tagfilter" style="cursor:pointer;">@recordTag</span>
}
<datalist id="tagList">
@foreach (string recordTag in recordTags)
{
<!option value="@recordTag"></!option>
}
</datalist>
</div>
<div>
<button onclick="showAddReminderModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add Reminder")</button>
@@ -33,34 +44,34 @@
<tr class="d-flex">
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Urgency")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Metric")</th>
<th scope="col" class="@(hasRefresh ? "col-4" : "col-5")">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Notes")</th>
<th scope="col" class="@(hasRefresh ? "col-3 col-md-4" : "col-5")">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 col-md-3">@translator.Translate(userLanguage, "Notes")</th>
@if (hasRefresh)
{
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Done")</th>
<th scope="col" class="col-2 col-md-1">@translator.Translate(userLanguage, "Done")</th>
}
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Delete")</th>
<th scope="col" class="col-2 col-md-1">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody>
@foreach (ReminderRecordViewModel reminderRecord in Model)
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@reminderRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditReminderRecordModal,@reminderRecord.Id)">
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@reminderRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditReminderRecordModal,@reminderRecord.Id)" data-tags='@string.Join(" ", reminderRecord.Tags)'>
@if (reminderRecord.Urgency == ReminderUrgency.VeryUrgent)
{
<td class="col-1"><span class="badge text-bg-danger">@translator.Translate(userLanguage, "Very Urgent")</span></td>
<td class="col-1"><span class="text-danger d-inline-block d-md-none"><i class="bi bi-hourglass-split h3"></i></span><span class="badge text-bg-danger d-none d-md-inline-block">@translator.Translate(userLanguage, "Very Urgent")</span></td>
}
else if (reminderRecord.Urgency == ReminderUrgency.Urgent)
{
<td class="col-1"><span class="badge text-bg-warning">@translator.Translate(userLanguage, "Urgent")</span></td>
<td class="col-1"><span class="text-warning d-inline-block d-md-none"><i class="bi bi-hourglass-split h3"></i></span><span class="badge text-bg-warning d-none d-md-inline-block">@translator.Translate(userLanguage, "Urgent")</span></td>
}
else if (reminderRecord.Urgency == ReminderUrgency.PastDue)
{
<td class="col-1"><span class="badge text-bg-secondary">@translator.Translate(userLanguage, "Past Due")</span></td>
<td class="col-1"><span class="text-secondary d-inline-block d-md-none"><i class="bi bi-hourglass-bottom h3"></i></span><span class="badge text-bg-secondary d-none d-md-inline-block">@translator.Translate(userLanguage, "Past Due")</span></td>
}
else
{
<td class="col-1"><span class="badge text-bg-success">@translator.Translate(userLanguage, "Not Urgent")</span></td>
<td class="col-1"><span class="text-success d-inline-block d-md-none"><i class="bi bi-hourglass-top h3"></i></span><span class="badge text-bg-success d-none d-md-inline-block">@translator.Translate(userLanguage, "Not Urgent")</span></td>
}
@if (reminderRecord.Metric == ReminderMetric.Date)
{
@@ -74,18 +85,18 @@
{
<td class="col-2">@reminderRecord.Metric</td>
}
<td class="@(hasRefresh ? "col-4" : "col-5")">@reminderRecord.Description</td>
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
<td class="@(hasRefresh ? "col-3 col-md-4" : "col-5")" data-record-type='cost'>@reminderRecord.Description</td>
<td class="col-2 col-md-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
@if (hasRefresh)
{
<td class="col-1 text-truncate">
<td class="col-2 col-md-1 text-truncate">
@if((reminderRecord.Urgency == ReminderUrgency.VeryUrgent || reminderRecord.Urgency == ReminderUrgency.PastDue) && reminderRecord.IsRecurring)
{
<button type="button" class="btn btn-secondary" onclick="markDoneReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-check-lg"></i></button>
}
</td>
}
<td class="col-1 text-truncate">
<td class="col-2 col-md-1 text-truncate">
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
</td>
</tr>

View File

@@ -12,7 +12,7 @@
<div class="row">
<div class="col-12">
<select class="form-select" id="yearOption" onchange="yearUpdated()">
<option value="0">All Time</option>
<option value="0">@translator.Translate(userLanguage, "All Time")</option>
@foreach (int year in Model.Years)
{
<option value="@year">@year</option>
@@ -32,7 +32,7 @@
<div class="col-12 col-md-10">
<div class="dropdown d-grid dropdown-center">
<button class="btn btn-outline-warning dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
Metrics
@translator.Translate(userLanguage, "Metrics")
</button>
<ul class="dropdown-menu" style="width:100%;">
<li class="dropdown-item">
@@ -117,6 +117,9 @@
</div>
</div>
<div class="col-md-3 col-12 chartContainer">
<div class="d-grid">
<button onclick="showGlobalSearch()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Search")<i class="bi ms-2 bi-search"></i></button>
</div>
<div class="d-grid">
<button onclick="generateVehicleHistoryReport()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Vehicle Maintenance Report")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
</div>
@@ -126,4 +129,25 @@
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="globalSearchModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="globalSearchModalContent">
<div class="modal-body">
<div class="input-group input-group-lg">
<input type="text" id="globalSearchInput" autocomplete="off" onkeyup="handleGlobalSearchKeyPress(event)" class="form-control" placeholder="@translator.Translate(userLanguage,"Search by Keyword(Case Sensitive)")">
<button type="button" class="btn btn-outline-secondary" onclick="performGlobalSearch()"><i class="bi bi-search"></i></button>
</div>
<div class="form-check form-switch mt-1">
<input class="form-check-input" type="checkbox" role="switch" id="globalSearchAutoSearchCheck" checked>
<label class="form-check-label" for="globalSearchAutoSearchCheck">@translator.Translate(userLanguage, "Incremental Search")</label>
</div>
<div id="globalSearchModalResults"></div>
</div>
</div>
</div>
</div>
<div id="vehicleHistoryReport" class="showOnPrint"></div>
<script>
getSelectedMetrics();
</script>

View File

@@ -8,7 +8,7 @@
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Service Record") : translator.Translate(userLanguage,"Edit Service Record"))</h5>
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Service Record") : translator.Translate(userLanguage, "Edit Service Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditServiceRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddServiceRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -22,8 +22,16 @@
<input type="text" id="serviceRecordDate" class="form-control" placeholder="@translator.Translate(userLanguage,"Date service was performed")" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="serviceRecordMileage">@translator.Translate(userLanguage,"Odometer")</label>
<label for="serviceRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
<div class="input-group">
<input type="number" inputmode="numeric" id="serviceRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"Odometer reading when serviced")" value="@(isNew ? "" : Model.Mileage)">
@if (isNew)
{
<div class="input-group-text">
<button type="button" class="btn btn-sm btn-primary zero-y-padding" onclick="getLastOdometerReadingAndIncrement('serviceRecordMileage')"><i class="bi bi-plus"></i></button>
</div>
}
</div>
<label for="serviceRecordDescription">@translator.Translate(userLanguage,"Description")</label>
<input type="text" id="serviceRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"Description of item(s) serviced(i.e. Oil Change)")" value="@Model.Description">
@if (isNew)
@@ -121,7 +129,8 @@
<script>
var uploadedFiles = [];
var selectedSupplies = [];
var recurringReminderRecordId = 0;
var recurringReminderRecordId = [];
var copySuppliesAttachments = false;
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)

View File

@@ -108,13 +108,13 @@
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 col-xl-1 flex-grow-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="cost" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-3 flex-grow-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@translator.Translate(userLanguage, "Odometer")</th>
<th scope="col" class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" onclick="toggleSort('servicerecord-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-3 flex-grow-1 flex-shrink-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
@@ -123,13 +123,23 @@
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@serviceRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditServiceRecordModal,@serviceRecord.Id)" data-tags='@string.Join(" ", serviceRecord.Tags)'>
<td class="col-2 col-xl-1 flex-grow-1" data-column="date" data-date="@StaticHelper.GetEpochFromDateTime(serviceRecord.Date)">@serviceRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1" data-column="odometer">@serviceRecord.Mileage</td>
<td class="col-3 col-xl-4 flex-grow-1" data-column="description">@serviceRecord.Description</td>
<td class="col-2 flex-grow-1" data-column="cost" data-record-type="cost">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
<td class="col-3 text-truncate flex-grow-1" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="odometer">@serviceRecord.Mileage</td>
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1" data-column="description">@serviceRecord.Description</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(serviceRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = serviceRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute)){
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
} else
{
@extraFieldValue
}
}
</td>
}
</tr>
}
@@ -159,6 +169,8 @@
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
</ul>
@if (userColumnPreferences.Any())
{

View File

@@ -8,7 +8,7 @@
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage,"Add New Supply Record") : translator.Translate(userLanguage,"Edit Supply Record"))</h5>
<h5 class="modal-title">@(isNew ? translator.Translate(userLanguage, "Add New Supply Record") : translator.Translate(userLanguage, "Edit Supply Record"))<small style="display:none; @(isNew ? "" : "cursor:pointer;")" class="cached-banner ms-2 text-warning" onclick='@(isNew ? "" : $"showEditSupplyRecordModal({Model.Id}, true)" )'>@translator.Translate(userLanguage, "Unsaved Changes")</small></h5>
<button type="button" class="btn-close" onclick="hideAddSupplyRecordModal()" aria-label="Close"></button>
</div>
<div class="modal-body">
@@ -34,7 +34,7 @@
<div class="input-group">
<input type="text" inputmode="decimal" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"Quantity")" value="@(isNew ? "1" : Model.Quantity)">
<div class="input-group-text">
<button type="button" class="btn btn-sm zero-y-padding btn-primary" onclick="replenishSupplies()">+</button>
<button type="button" class="btn btn-sm zero-y-padding btn-primary" onclick="replenishSupplies()"><i class="bi bi-plus"></i></button>
</div>
</div>
</div>

View File

@@ -120,15 +120,15 @@
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-2 flex-grow-1 col-xl-1" data-column="date">@translator.Translate(userLanguage, "Date")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="partnumber">@translator.Translate(userLanguage, "Part #")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="supplier">@translator.Translate(userLanguage, "Supplier")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="partnumber">@translator.Translate(userLanguage, "Part #")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="supplier">@translator.Translate(userLanguage, "Supplier")</th>
<th scope="col" class="col-2 flex-grow-1 col-xl-3" data-column="description">@translator.Translate(userLanguage, "Description")</th>
<th scope="col" class="col-1 flex-grow-1" 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" data-column="cost" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-2 flex-grow-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
<th scope="col" class="col-1 flex-grow-1 flex-shrink-1" 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" data-column="cost" onclick="toggleSort('supply-tab-pane', this)" style="cursor:pointer;">@translator.Translate(userLanguage, "Cost")</th>
<th scope="col" class="col-2 flex-grow-1 flex-shrink-1" data-column="notes">@translator.Translate(userLanguage, "Notes")</th>
@foreach (string extraFieldColumn in extraFields)
{
<th scope="col" style='display:none;' class="col-2 flex-grow-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
<th scope="col" style='display:none;' class="col-2 flex-grow-1 flex-shrink-1" data-column="@extraFieldColumn">@extraFieldColumn</th>
}
</tr>
</thead>
@@ -137,15 +137,27 @@
{
<tr class="d-flex user-select-none" style="cursor:pointer;" onmouseup="stopEvent()" ontouchstart="detectRowLongTouch(this)" ontouchend="detectRowTouchEndPremature(this)" data-rowId="@supplyRecord.Id" oncontextmenu="showTableContextMenu(this)" onmousemove="rangeMouseMove(this)" onclick="handleTableRowClick(this, showEditSupplyRecordModal,@supplyRecord.Id)" data-tags='@string.Join(" ", supplyRecord.Tags)'>
<td class="col-2 flex-grow-1 col-xl-1" data-column="date">@supplyRecord.Date.ToShortDateString()</td>
<td class="col-2 flex-grow-1" data-column="partnumber">@supplyRecord.PartNumber</td>
<td class="col-2 flex-grow-1" data-column="supplier">@supplyRecord.PartSupplier</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="partnumber">@supplyRecord.PartNumber</td>
<td class="col-2 flex-grow-1 flex-shrink-1" data-column="supplier">@supplyRecord.PartSupplier</td>
<td class="col-2 flex-grow-1 col-xl-3" data-column="description">@supplyRecord.Description</td>
<td class="col-1 flex-grow-1" data-column="quantity">@supplyRecord.Quantity</td>
<td class="col-1 flex-grow-1" data-column="cost" data-record-type="cost">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
<td class="col-2 flex-grow-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="quantity">@supplyRecord.Quantity</td>
<td class="col-1 flex-grow-1 flex-shrink-1" data-column="cost" data-record-type="cost">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
@foreach (string extraFieldColumn in extraFields)
{
<td class="col-2 flex-grow-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">@(supplyRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "")</td>
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
@{
var extraFieldValue = supplyRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
{
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
}
else
{
@extraFieldValue
}
}
</td>
}
</tr>
}

View File

@@ -33,10 +33,12 @@
$('#upgradeRecordCost').val(selectedSupplyResult.totalSum);
break;
case "PlanRecord":
case "PlanRecordTemplate":
$('#planRecordCost').val(selectedSupplyResult.totalSum);
break;
}
selectedSupplies = getSuppliesAndQuantity().selectedSupplies;
copySuppliesAttachments = $("#inputCopySuppliesAttachments").is(':checked');
hideSuppliesModal();
}
function hideParentModal(){
@@ -52,6 +54,7 @@
$('#upgradeRecordModal').modal('hide');
break;
case "PlanRecord":
case "PlanRecordTemplate":
$('#planRecordModal').modal('hide');
break;
}
@@ -69,6 +72,7 @@
$('#upgradeRecordModal').modal('show');
break;
case "PlanRecord":
case "PlanRecordTemplate":
$('#planRecordModal').modal('show');
break;
}
@@ -82,6 +86,21 @@
}
}
function getSupplies() {
var caller = GetCaller().tab;
if (caller == 'PlanRecordTemplate') {
var planRecordTemplateId = getPlanRecordModelData().id;
$.get(`/Vehicle/GetSupplyRecordsForPlanRecordTemplate?planRecordTemplateId=${planRecordTemplateId}`, function (data) {
if (data) {
hideParentModal();
$("#inputSuppliesModalContent").html(data);
$('#inputSuppliesModal').modal('show');
recalculateTotal();
if (copySuppliesAttachments) {
$('#inputCopySuppliesAttachments').attr('checked', true);
}
}
});
} else {
var vehicleId = GetVehicleId().vehicleId;
$.get(`/Vehicle/GetSupplyRecordsForRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {
@@ -89,7 +108,8 @@
$("#inputSuppliesModalContent").html(data);
$('#inputSuppliesModal').modal('show');
}
})
});
}
}
function hideSuppliesModal() {
$('#inputSuppliesModal').modal('hide');

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