├── .gitignore ├── Demo(2021-01-01).mp4 ├── README.md ├── TimeTracker └── TimeTracker │ ├── .gitignore │ ├── ClientApp │ ├── .browserslistrc │ ├── .env.development │ ├── .env.production │ ├── .eslintrc.js │ ├── .gitignore │ ├── Example.vue │ ├── README.md │ ├── babel.config.js │ ├── dist │ │ ├── css │ │ │ ├── app.4b493da0.css │ │ │ ├── app.4b493da0.css.map │ │ │ ├── chunk-19e1a676.545f8971.css │ │ │ ├── chunk-19e1a676.545f8971.css.map │ │ │ ├── chunk-1b599696.0cd42b2a.css │ │ │ ├── chunk-1b599696.0cd42b2a.css.map │ │ │ ├── chunk-2d48622d.791d3332.css │ │ │ ├── chunk-2d48622d.791d3332.css.map │ │ │ ├── chunk-56927b24.a0d06f03.css │ │ │ ├── chunk-56927b24.a0d06f03.css.map │ │ │ ├── chunk-6568d9c2.e137935d.css │ │ │ ├── chunk-6568d9c2.e137935d.css.map │ │ │ ├── chunk-c7002a8e.b7228bf6.css │ │ │ ├── chunk-c7002a8e.b7228bf6.css.map │ │ │ ├── chunk-da9561ba.787b4019.css │ │ │ ├── chunk-da9561ba.787b4019.css.map │ │ │ ├── chunk-vendors.5605a41b.css │ │ │ └── chunk-vendors.5605a41b.css.map │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── MaterialIcons-Regular.012cf6a1.woff │ │ │ ├── MaterialIcons-Regular.0509ab09.0509ab09.woff2 │ │ │ ├── MaterialIcons-Regular.29b882f0.29b882f0.woff │ │ │ ├── MaterialIcons-Regular.570eb838.woff2 │ │ │ ├── MaterialIcons-Regular.96c47680.96c47680.eot │ │ │ ├── MaterialIcons-Regular.a37b0c01.ttf │ │ │ ├── MaterialIcons-Regular.d120c85b.d120c85b.ttf │ │ │ ├── MaterialIcons-Regular.e79bfd88.eot │ │ │ ├── materialdesignicons-webfont.3d1f8fa2.eot │ │ │ ├── materialdesignicons-webfont.3e722fd5.ttf │ │ │ ├── materialdesignicons-webfont.4187121a.woff2 │ │ │ └── materialdesignicons-webfont.fec1b66a.woff │ │ ├── img │ │ │ ├── dark_wood.d35f4a25.png │ │ │ ├── forest2.98aeac27.jpg │ │ │ ├── grey_wash_wall.88ab8ad9.png │ │ │ ├── icons │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ ├── android-chrome-512x512.png │ │ │ │ ├── android-chrome-maskable-192x192.png │ │ │ │ ├── android-chrome-maskable-512x512.png │ │ │ │ ├── apple-touch-icon-120x120.png │ │ │ │ ├── apple-touch-icon-152x152.png │ │ │ │ ├── apple-touch-icon-180x180.png │ │ │ │ ├── apple-touch-icon-60x60.png │ │ │ │ ├── apple-touch-icon-76x76.png │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── msapplication-icon-144x144.png │ │ │ │ ├── mstile-150x150.png │ │ │ │ └── safari-pinned-tab.svg │ │ │ ├── whitey.db698606.png │ │ │ └── xv_light_blue.9693a7e2.png │ │ ├── index.html │ │ ├── js │ │ │ ├── app.edd198db.js │ │ │ ├── app.edd198db.js.map │ │ │ ├── chunk-19e1a676.e3729111.js │ │ │ ├── chunk-19e1a676.e3729111.js.map │ │ │ ├── chunk-1b599696.103c08dd.js │ │ │ ├── chunk-1b599696.103c08dd.js.map │ │ │ ├── chunk-2d0c5b16.3a4e24dd.js │ │ │ ├── chunk-2d0c5b16.3a4e24dd.js.map │ │ │ ├── chunk-2d0c9224.ffd16665.js │ │ │ ├── chunk-2d0c9224.ffd16665.js.map │ │ │ ├── chunk-2d48622d.40df08ef.js │ │ │ ├── chunk-2d48622d.40df08ef.js.map │ │ │ ├── chunk-455734fe.fb5bdc63.js │ │ │ ├── chunk-455734fe.fb5bdc63.js.map │ │ │ ├── chunk-56927b24.e3053918.js │ │ │ ├── chunk-56927b24.e3053918.js.map │ │ │ ├── chunk-6568d9c2.a8fe224d.js │ │ │ ├── chunk-6568d9c2.a8fe224d.js.map │ │ │ ├── chunk-c7002a8e.ad326856.js │ │ │ ├── chunk-c7002a8e.ad326856.js.map │ │ │ ├── chunk-da9561ba.5704b6b6.js │ │ │ ├── chunk-da9561ba.5704b6b6.js.map │ │ │ ├── chunk-vendors.afce3364.js │ │ │ └── chunk-vendors.afce3364.js.map │ │ ├── manifest.json │ │ ├── precache-manifest.2ccd831319ba6bd71793731fef02f545.js │ │ ├── robots.txt │ │ └── service-worker.js │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── img │ │ │ └── icons │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ ├── android-chrome-512x512.png │ │ │ │ ├── android-chrome-maskable-192x192.png │ │ │ │ ├── android-chrome-maskable-512x512.png │ │ │ │ ├── apple-touch-icon-120x120.png │ │ │ │ ├── apple-touch-icon-152x152.png │ │ │ │ ├── apple-touch-icon-180x180.png │ │ │ │ ├── apple-touch-icon-60x60.png │ │ │ │ ├── apple-touch-icon-76x76.png │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── msapplication-icon-144x144.png │ │ │ │ ├── mstile-150x150.png │ │ │ │ └── safari-pinned-tab.svg │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── App.vue │ │ ├── api │ │ │ ├── admin.ts │ │ │ ├── authentication.ts │ │ │ ├── imges.ts │ │ │ ├── parameter.ts │ │ │ ├── period.ts │ │ │ ├── taskEditor.ts │ │ │ ├── taskReporter.ts │ │ │ └── webSocket.ts │ │ ├── assets │ │ │ ├── img │ │ │ │ ├── always_grey.png │ │ │ │ ├── blue_wash_wall_1.png │ │ │ │ ├── blue_wash_wall_2.png │ │ │ │ ├── blue_wash_wall_3.png │ │ │ │ ├── blue_wash_wall_4.png │ │ │ │ ├── crossword.png │ │ │ │ ├── dark_wood.png │ │ │ │ ├── dust_scratches.png │ │ │ │ ├── fabric_of_squares_gray.png │ │ │ │ ├── footer_lodyas.png │ │ │ │ ├── footer_lodyas2.png │ │ │ │ ├── forest │ │ │ │ │ ├── forest1.jpg │ │ │ │ │ └── forest2.jpg │ │ │ │ ├── grey_wash_wall.png │ │ │ │ ├── noisy_grid.png │ │ │ │ ├── noisy_grid2.png │ │ │ │ ├── pikachou.gif │ │ │ │ ├── project_papper.png │ │ │ │ ├── triangular.png │ │ │ │ ├── vichy-dark.png │ │ │ │ ├── vichy.png │ │ │ │ ├── whitey copy.png │ │ │ │ ├── whitey-light.png │ │ │ │ ├── whitey.png │ │ │ │ ├── xv.png │ │ │ │ └── xv_light_blue.png │ │ │ └── logo.png │ │ ├── components │ │ │ ├── GitHubCorner.vue │ │ │ ├── PageFlipper.vue │ │ │ ├── auth │ │ │ │ ├── AdminAccounts.vue │ │ │ │ ├── AdminUncheckAccounts.vue │ │ │ │ ├── PageIdle.vue │ │ │ │ ├── PageIdleModalSimple.vue │ │ │ │ ├── Register.vue │ │ │ │ ├── Settings.vue │ │ │ │ ├── SettingsImageEditor.vue │ │ │ │ ├── SignIn.vue │ │ │ │ └── SignInModal.vue │ │ │ ├── card │ │ │ │ └── GeneralCard.vue │ │ │ ├── clock │ │ │ │ ├── AnalogClock.vue │ │ │ │ ├── Clock.vue │ │ │ │ └── DigitalClock.vue │ │ │ ├── img │ │ │ │ ├── SimpleImageEditor.vue │ │ │ │ └── SimpleImageEditorModal.vue │ │ │ ├── nav │ │ │ │ ├── Nav.vue │ │ │ │ ├── NavGithubLink.vue │ │ │ │ ├── NavIndividualSettings.vue │ │ │ │ ├── NavItem.vue │ │ │ │ ├── NavLinkItem.vue │ │ │ │ ├── NavProgress.vue │ │ │ │ └── TwoColumnSidebarToggleIcon.vue │ │ │ └── trackTask │ │ │ │ ├── charts │ │ │ │ ├── EchartsPie.vue │ │ │ │ ├── PieRowData.vue │ │ │ │ ├── TaskPeriodEchartsLine.vue │ │ │ │ ├── TaskPeriodSimpleSummary.vue │ │ │ │ ├── greenTheme.json │ │ │ │ └── taskPeriodSimpleSummary.ts │ │ │ │ ├── taskForm │ │ │ │ ├── TaskDayForm.vue │ │ │ │ ├── TaskDayLeaveCheckbox.vue │ │ │ │ ├── TaskDayLeaveIcon.vue │ │ │ │ ├── TaskDayTimeline.vue │ │ │ │ ├── TaskDayTimelineTitle.vue │ │ │ │ └── taskform.ts │ │ │ │ ├── toolbar │ │ │ │ ├── DateMenuSelector.vue │ │ │ │ ├── DatePeriodSelector.vue │ │ │ │ ├── DatePicker.vue │ │ │ │ ├── DateRangeSelector.vue │ │ │ │ ├── MetaDisplayer.vue │ │ │ │ └── TargetModalSelector.vue │ │ │ │ └── trackSettings │ │ │ │ ├── DatePeriodForm.vue │ │ │ │ ├── DatePeriodFormAdd.vue │ │ │ │ ├── DatePeriodFormUpdate.vue │ │ │ │ ├── OptionForm.vue │ │ │ │ ├── OptionFormAdd.vue │ │ │ │ └── OptionFormUpdate.vue │ │ ├── configGtag.ts │ │ ├── jctk-table-form.d.ts │ │ ├── main.ts │ │ ├── models │ │ │ ├── authentication.ts │ │ │ ├── charts.ts │ │ │ ├── constants │ │ │ │ ├── authentication.ts │ │ │ │ ├── task.ts │ │ │ │ └── webSocket.ts │ │ │ ├── nav.ts │ │ │ ├── notification.ts │ │ │ ├── pageIdle.ts │ │ │ ├── parameter.ts │ │ │ ├── period.ts │ │ │ ├── routeConfigs.ts │ │ │ ├── store.ts │ │ │ ├── tasks.ts │ │ │ ├── twoColumn.ts │ │ │ └── webSocket.ts │ │ ├── registerServiceWorker.ts │ │ ├── router │ │ │ ├── index.ts │ │ │ ├── routeConfigs.ts │ │ │ └── routeRoleValidation.ts │ │ ├── scss │ │ │ ├── example.scss │ │ │ ├── global_var.scss │ │ │ └── var.scss │ │ ├── shims-tsx.d.ts │ │ ├── shims-vue.d.ts │ │ ├── store │ │ │ ├── authentication.ts │ │ │ ├── index.ts │ │ │ ├── notification.ts │ │ │ ├── pageIdle.ts │ │ │ ├── taskParameters.ts │ │ │ ├── twoColumn.ts │ │ │ └── webSocket.ts │ │ ├── util │ │ │ ├── apiHandler.ts │ │ │ ├── authValidation.ts │ │ │ ├── authentication.ts │ │ │ ├── breakPoint.ts │ │ │ ├── components │ │ │ │ ├── layout │ │ │ │ │ └── ResponsiveElement.vue │ │ │ │ └── transition │ │ │ │ │ ├── RippleTransition.vue │ │ │ │ │ ├── RippleTransitionFlip.vue │ │ │ │ │ └── SimpleTransition.vue │ │ │ ├── notification.ts │ │ │ ├── period.ts │ │ │ ├── taskDate.ts │ │ │ └── taskParameters.ts │ │ ├── views │ │ │ ├── Home.vue │ │ │ ├── IndividualSettings.vue │ │ │ ├── PermissionEditor.vue │ │ │ ├── Registration.vue │ │ │ ├── TaskEditor.vue │ │ │ ├── TaskReporter.vue │ │ │ ├── TaskSummaryReporter.vue │ │ │ ├── TrackSettings.vue │ │ │ └── layouts │ │ │ │ ├── Center.vue │ │ │ │ ├── Container.vue │ │ │ │ ├── Tab.vue │ │ │ │ └── TwoColumn.vue │ │ ├── vue-echarts.d.ts │ │ ├── vuetify-image-input.d.ts │ │ └── vuetify.ts │ ├── tests │ │ └── unit │ │ │ └── example.spec.ts │ ├── tsconfig.json │ ├── vue.config.js │ └── webpack.config.js │ ├── Controllers │ ├── AccountController.cs │ ├── AdminController.cs │ ├── ImageController.cs │ ├── ParameterController.cs │ ├── PeriodController.cs │ ├── TaskEditorController.cs │ └── TaskReporterController.cs │ ├── DAL │ ├── ApplicationDbContext.cs │ ├── Attributes │ │ ├── SqlDefaultValueAttribute.cs │ │ └── UniqueAttribute.cs │ ├── DBModels │ │ ├── Auth │ │ │ ├── MapUserRole.cs │ │ │ ├── User.cs │ │ │ ├── UserImage.cs │ │ │ └── UserRole.cs │ │ ├── Task │ │ │ ├── DayWorkLimitTime.cs │ │ │ ├── NonWorkDays.cs │ │ │ ├── Period.cs │ │ │ ├── Task.cs │ │ │ ├── TaskDay.cs │ │ │ ├── TaskSource.cs │ │ │ ├── TaskTimeRange.cs │ │ │ └── TaskType.cs │ │ └── UserRole.cs │ ├── DBModelsBase │ │ ├── EntityDateInfo.cs │ │ ├── EntityOptions.cs │ │ ├── EntityTask.cs │ │ └── EntityTaskBase.cs │ ├── IUserAuthHandler.cs │ ├── Models │ │ ├── AccountStatus.cs │ │ ├── CreateAccountResult.cs │ │ └── UserRoles.cs │ ├── ParameterHandler.cs │ ├── SeedData.cs │ ├── SeedDataImg.cs │ ├── TaskHandler.cs │ ├── TaskReportHandler.cs │ └── UserAuthHandler.cs │ ├── Helper │ ├── Attributes │ │ ├── DateFormatConverter.cs │ │ ├── GuidNotEmptyAttribute.cs │ │ └── ListNotEmptyAttribute.cs │ ├── Auth │ │ ├── AuthorizeRoleAttribute.cs │ │ └── SecurePasswordHasher.cs │ └── Extensions │ │ ├── DBContextExtension.cs │ │ ├── DateTimeExtension.cs │ │ ├── SessionAuthExtensions.cs │ │ ├── SessionExtensions.cs │ │ └── SqlServiceCollectionExtensions.cs │ ├── Hubs │ ├── BaseWSHub.cs │ ├── Models │ │ └── WSMapCode.cs │ ├── UserIdProvider.cs │ ├── WSHub.cs │ └── WSHubHandler.cs │ ├── MappingProfile.cs │ ├── Migrations │ ├── 20201226131516_InitialCreate.Designer.cs │ ├── 20201226131516_InitialCreate.cs │ └── ApplicationDbContextModelSnapshot.cs │ ├── Models │ ├── ApprovedUser.cs │ ├── AuthenticationUser.cs │ ├── Period │ │ └── QueryPeriod.cs │ ├── RegistUser.cs │ ├── SessionUserData.cs │ ├── Task │ │ ├── CreateTask.cs │ │ ├── DeleteTasks.cs │ │ ├── GetDaysDataResponse.cs │ │ ├── MultiSimpleSummary.cs │ │ ├── PersonSummary.cs │ │ ├── PieRow.cs │ │ ├── QueryPeopleTasks.cs │ │ ├── QueryTasks.cs │ │ ├── SimpleSummary.cs │ │ ├── UpdateIsLeave.cs │ │ ├── UpdateTaskCol.cs │ │ └── UpdateTaskRowOrder.cs │ ├── UpdateAccount.cs │ ├── UpdatePassword.cs │ ├── UpdateUser.cs │ └── UserInfo.cs │ ├── Pages │ ├── Error.cshtml │ ├── Error.cshtml.cs │ └── _ViewImports.cshtml │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── TimeTracker.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── release_windows ├── CreateDB_CreateTable_CreateInitData.sql └── publish.zip └── screen_shot.png /Demo(2021-01-01).mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/Demo(2021-01-01).mp4 -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_SERVER_URL=https://localhost:44328/ -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_SERVER_URL=/ 2 | VUE_APP_GA_ID=UA-138418210-2 -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 17 | "@typescript-eslint/interface-name-prefix": 'off', 18 | }, 19 | overrides: [ 20 | { 21 | files: [ 22 | '**/__tests__/*.{j,t}s?(x)', 23 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 24 | ], 25 | env: { 26 | jest: true 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/Example.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # test-composition 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your unit tests 19 | ``` 20 | npm run test:unit 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/css/chunk-6568d9c2.e137935d.css: -------------------------------------------------------------------------------- 1 | hr.vm-style-1[data-v-55194cea]{border:0;height:1px;background-image:linear-gradient(90deg,transparent,rgba(0,0,0,.75),transparent)}hr.vm-style-1-2[data-v-55194cea]{border:0;height:1px;background-image:linear-gradient(90deg,transparent,#496586,transparent)}hr.vm-style-2[data-v-55194cea]{height:10px;border:1;box-shadow:inset 0 9px 9px -3px rgba(0,0,0,.5);border-radius:5px}hr.vm-style-3[data-v-55194cea]{border:0;height:0;box-shadow:0 0 10px 1px #000}hr.vm-style-3[data-v-55194cea]:after{content:"\00a0"}.text-shadow-1[data-v-55194cea]{text-shadow:2px 2px 2px #aaa}.text-shadow-2[data-v-55194cea]{text-shadow:1px 1px 0 #fff,2px 2px 0 #000}.drop-shadow-1[data-v-55194cea]{filter:drop-shadow(12px 12px 7px rgba(0,0,0,.701961))}.drop-shadow-2[data-v-55194cea]{filter:drop-shadow(5px 5px 6px rgba(0,0,0,.701961))}i.material-icons[data-v-55194cea]::-moz-selection{color:initial}i.material-icons[data-v-55194cea]::selection{color:initial}[data-v-55194cea]{box-sizing:border-box}body[data-v-55194cea]{display:block;margin:0}hr.vm-test[data-v-55194cea]{margin:0 25px;height:500px;width:1px;background:#000;background:-webkit-gradient(linear,180 180,100% 0,from(#fff),to(#fff),color-stop(50%,#000))}.flex-center[data-v-55194cea]{display:flex;justify-content:center}.margin-center[data-v-55194cea]{margin:0 auto}.theme-table.theme--light.v-data-table tbody tr.v-data-table__selected[data-v-55194cea]{background:#607d8b;color:#d3d3d3}.theme-table.theme--light.v-data-table tbody tr.v-data-table__selected[data-v-55194cea]:hover{background:#009688!important;color:#d3d3d3!important}html[data-v-55194cea]{overflow:auto;overflow-y:auto!important}html[data-v-55194cea]::-webkit-scrollbar{width:14px;height:12px;background-color:#eaeaea}html[data-v-55194cea]::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);background-color:#d8d8d8}html[data-v-55194cea]::-webkit-scrollbar-thumb:hover{background-color:#ecebeb}.animate__animated.animate__fastest[data-v-55194cea]{-webkit-animation-duration:.25s;animation-duration:.25s}.user-image-nav[data-v-55194cea]{height:48px;width:48px;border-radius:48px}.user-image-big[data-v-55194cea]{height:256px;width:256px;border-radius:256px}.TaskSummaryReporter[data-v-55194cea]{width:100%} 2 | /*# sourceMappingURL=chunk-6568d9c2.e137935d.css.map */ -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/favicon.ico -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.012cf6a1.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.012cf6a1.woff -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.0509ab09.0509ab09.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.0509ab09.0509ab09.woff2 -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.29b882f0.29b882f0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.29b882f0.29b882f0.woff -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.570eb838.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.570eb838.woff2 -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.96c47680.96c47680.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.96c47680.96c47680.eot -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.a37b0c01.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.a37b0c01.ttf -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.d120c85b.d120c85b.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.d120c85b.d120c85b.ttf -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.e79bfd88.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/MaterialIcons-Regular.e79bfd88.eot -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.3d1f8fa2.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.3d1f8fa2.eot -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.3e722fd5.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.3e722fd5.ttf -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.4187121a.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.4187121a.woff2 -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.fec1b66a.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/fonts/materialdesignicons-webfont.fec1b66a.woff -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/dark_wood.d35f4a25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/dark_wood.d35f4a25.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/forest2.98aeac27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/forest2.98aeac27.jpg -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/grey_wash_wall.88ab8ad9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/grey_wash_wall.88ab8ad9.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/whitey.db698606.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/whitey.db698606.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/img/xv_light_blue.9693a7e2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/dist/img/xv_light_blue.9693a7e2.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/index.html: -------------------------------------------------------------------------------- 1 | TimeTracker
-------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/js/chunk-2d0c5b16.3a4e24dd.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0c5b16"],{"3fd1":function(e,n,t){"use strict";t.r(n);var a=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("Container",[t("Center",{staticClass:"Registration"},[t("Register")],1)],1)},r=[],s=t("a6f4"),i=t("3d0f"),o=t("9fb8"),c=t("f102"),f=Object(s["defineComponent"])({name:"Registration",props:{},components:{Container:i["a"],Center:o["a"],Register:c["a"]}}),p=f,u=t("2877"),d=Object(u["a"])(p,a,r,!1,null,"d7e79828",null);n["default"]=d.exports}}]); 2 | //# sourceMappingURL=chunk-2d0c5b16.3a4e24dd.js.map -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/js/chunk-2d0c5b16.3a4e24dd.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///./src/views/Registration.vue?48b3","webpack:///./src/views/Registration.vue","webpack:///./src/views/Registration.vue?f379","webpack:///./src/views/Registration.vue?3428"],"names":["render","_vm","this","_h","$createElement","_c","_self","staticClass","staticRenderFns","name","props","components","Container","Center","Register","component"],"mappings":"yHAAA,IAAIA,EAAS,WAAa,IAAIC,EAAIC,KAASC,EAAGF,EAAIG,eAAmBC,EAAGJ,EAAIK,MAAMD,IAAIF,EAAG,OAAOE,EAAG,YAAY,CAACA,EAAG,SAAS,CAACE,YAAY,gBAAgB,CAACF,EAAG,aAAa,IAAI,IAC1KG,EAAkB,G,gDCcP,+BAAgB,CAC3BC,KAAM,eACNC,MAAM,GAGNC,WAAW,CACPC,YAAA,KACAC,SAAA,KACAC,WAAA,QCvB+X,I,YCOnYC,EAAY,eACd,EACAf,EACAQ,GACA,EACA,KACA,WACA,MAIa,aAAAO,E","file":"js/chunk-2d0c5b16.3a4e24dd.js","sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('Container',[_c('Center',{staticClass:\"Registration\"},[_c('Register')],1)],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n\n\n\n\nimport { defineComponent, ref } from '@vue/composition-api'\n\nimport Container from './layouts/Container.vue'\nimport Center from './layouts/Center.vue'\nimport Register from '@/components/auth/Register.vue'\n\nexport default defineComponent({\n name: 'Registration',\n props:{\n\n },\n components:{\n Container,\n Center,\n Register\n }, \n})\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--14-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??ref--14-3!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Registration.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--14-0!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??ref--14-3!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Registration.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Registration.vue?vue&type=template&id=d7e79828&scoped=true&\"\nimport script from \"./Registration.vue?vue&type=script&lang=ts&\"\nexport * from \"./Registration.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"d7e79828\",\n null\n \n)\n\nexport default component.exports"],"sourceRoot":""} -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/js/chunk-6568d9c2.a8fe224d.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6568d9c2"],{"153c":function(e,t,n){"use strict";n.r(t);var a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("Container",[n("div",{staticClass:"TaskSummaryReporter"},[n("TaskReporter",{attrs:{multipleMode:""}})],1)])},r=[],o=n("a6f4"),s=n("3d0f"),c=n("9944"),u=Object(o["defineComponent"])({name:"TaskSummaryReporter",props:{},components:{Container:s["a"],TaskReporter:c["default"]}}),i=u,p=(n("eb85"),n("2877")),l=Object(p["a"])(i,a,r,!1,null,"55194cea",null);t["default"]=l.exports},be0e:function(e,t,n){},eb85:function(e,t,n){"use strict";n("be0e")}}]); 2 | //# sourceMappingURL=chunk-6568d9c2.a8fe224d.js.map -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"TimeTracker","short_name":"TimeTracker","theme_color":"#4DBA87","icons":[{"src":"./img/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"./img/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"},{"src":"./img/icons/android-chrome-maskable-192x192.png","sizes":"192x192","type":"image/png","purpose":"maskable"},{"src":"./img/icons/android-chrome-maskable-512x512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":".","display":"standalone","background_color":"#000000"} -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/dist/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.2ccd831319ba6bd71793731fef02f545.js" 18 | ); 19 | 20 | workbox.core.setCacheNameDetails({prefix: "TimeTracker"}); 21 | 22 | self.addEventListener('message', (event) => { 23 | if (event.data && event.data.type === 'SKIP_WAITING') { 24 | self.skipWaiting(); 25 | } 26 | }); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel' 3 | } 4 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TimeTracker", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "@vue/composition-api": "^1.0.0-beta.20", 13 | "ci": "^1.0.0", 14 | "core-js": "^3.6.5", 15 | "register-service-worker": "^1.7.1", 16 | "vue": "^2.6.11", 17 | "vue-router": "^3.2.0", 18 | "vuex": "^3.4.0" 19 | }, 20 | "devDependencies": { 21 | "@mdi/font": "^5.8.55", 22 | "@microsoft/signalr": "^5.0.0", 23 | "@types/echarts": "^4.9.3", 24 | "@types/jest": "^24.0.19", 25 | "@types/jquery": "^3.5.4", 26 | "@types/lodash.debounce": "^4.0.6", 27 | "@types/lodash.throttle": "^4.1.6", 28 | "@typescript-eslint/eslint-plugin": "^2.33.0", 29 | "@typescript-eslint/parser": "^2.33.0", 30 | "@vue/cli-plugin-babel": "~4.5.0", 31 | "@vue/cli-plugin-eslint": "~4.5.0", 32 | "@vue/cli-plugin-pwa": "~4.5.0", 33 | "@vue/cli-plugin-router": "~4.5.0", 34 | "@vue/cli-plugin-typescript": "~4.5.0", 35 | "@vue/cli-plugin-unit-jest": "~4.5.0", 36 | "@vue/cli-plugin-vuex": "~4.5.0", 37 | "@vue/cli-service": "~4.5.0", 38 | "@vue/eslint-config-typescript": "^5.0.2", 39 | "@vue/test-utils": "^1.0.3", 40 | "animate.css": "^4.1.1", 41 | "axios": "^0.21.0", 42 | "echarts": "^4.9.0", 43 | "eslint": "^6.7.2", 44 | "eslint-plugin-vue": "^6.2.2", 45 | "jctk-table-form": "^0.1.11", 46 | "jquery": "^3.5.1", 47 | "lodash.debounce": "^4.0.8", 48 | "lodash.throttle": "^4.1.1", 49 | "material-design-icons": "^3.0.1", 50 | "moment": "^2.29.1", 51 | "node-sass": "^4.12.0", 52 | "sass-loader": "^8.0.2", 53 | "typescript": "~3.9.3", 54 | "velocity-animate": "^1.5.2", 55 | "vue-analog-clock": "^1.0.2", 56 | "vue-clickaway2": "^2.3.2", 57 | "vue-echarts": "^4.1.0", 58 | "vue-gtag": "^1.10.0", 59 | "vue-template-compiler": "^2.6.11", 60 | "vue-toastification": "^1.7.8", 61 | "vue-use-web": "^1.0.1", 62 | "vuetify": "^2.3.19", 63 | "vuetify-image-input": "^19.1.0", 64 | "vuex-multi-tab-state": "^1.0.15" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/favicon.ico -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/api/admin.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios' 2 | import { Ref } from '@vue/composition-api' 3 | 4 | import { APIHandler } from '@/util/apiHandler.ts' 5 | import { Store } from 'vuex/types/index' 6 | import VueRouter from 'vue-router' 7 | import { IStore } from '@/models/store' 8 | import { IUpdateAccounts } from '@/models/authentication' 9 | 10 | class AdminAPIHandler extends APIHandler{ 11 | 12 | constructor( store: Store, router: VueRouter ) { 13 | super(store, router) 14 | } 15 | 16 | GetUncheckAccounts(isLoading: Ref, 17 | SuccessFunc?: (response: AxiosResponse) => void, 18 | ErrorFunc?: (error: any) => void){ 19 | this.HttpPost('api/Admin/GetUncheckAccounts', isLoading, undefined, SuccessFunc, ErrorFunc) 20 | } 21 | 22 | GetAccounts(isLoading: Ref, 23 | SuccessFunc?: (response: AxiosResponse) => void, 24 | ErrorFunc?: (error: any) => void){ 25 | this.HttpPost('api/Admin/GetAccounts', isLoading, undefined, SuccessFunc, ErrorFunc) 26 | } 27 | 28 | UpdateAccounts(data: Array, isLoading: Ref, 29 | SuccessFunc?: (response: AxiosResponse) => void, 30 | ErrorFunc?: (error: any) => void){ 31 | this.HttpPost('api/Admin/UpdateAccounts', isLoading, data, SuccessFunc, ErrorFunc) 32 | } 33 | } 34 | 35 | export { 36 | AdminAPIHandler 37 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/api/imges.ts: -------------------------------------------------------------------------------- 1 | import { IStore } from "@/models/store" 2 | import { APIHandler } from "@/util/apiHandler" 3 | import { Ref } from "@vue/composition-api" 4 | import { AxiosResponse } from "axios" 5 | import VueRouter from "vue-router" 6 | import { Store } from "vuex/types/index" 7 | 8 | 9 | class ImageAPIHandler extends APIHandler{ 10 | 11 | constructor( store: Store, router: VueRouter ) { 12 | super(store, router) 13 | } 14 | 15 | GetImage(isLoading?: Ref, 16 | SuccessFunc?: (response: AxiosResponse) => void, 17 | ErrorFunc?: (error: any) => void){ 18 | this.HttpPost('api/Image/GetImage', isLoading, undefined, (response) => { 19 | this._store.commit("SetUserImage", response.data) 20 | if (SuccessFunc) { 21 | SuccessFunc(response) 22 | } 23 | }, ErrorFunc) 24 | } 25 | 26 | CreateOrUpdateImage(data: string, isLoading: Ref, 27 | SuccessFunc?: (response: AxiosResponse) => void, 28 | ErrorFunc?: (error: any) => void){ 29 | this.HttpPost('api/Image/CreateOrUpdateImage', isLoading, { image: data }, (response) => { 30 | this._store.commit("SetUserImage", data) 31 | if (SuccessFunc) { 32 | SuccessFunc(response) 33 | } 34 | }, ErrorFunc) 35 | } 36 | 37 | DeleteImage(isLoading: Ref, 38 | SuccessFunc?: (response: AxiosResponse) => void, 39 | ErrorFunc?: (error: any) => void){ 40 | this.HttpPost('api/Image/DeleteImage', isLoading, undefined, (response) => { 41 | this._store.commit("SetUserImage", "") 42 | if (SuccessFunc) { 43 | SuccessFunc(response) 44 | } 45 | }, ErrorFunc) 46 | } 47 | } 48 | 49 | export { 50 | ImageAPIHandler 51 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/api/period.ts: -------------------------------------------------------------------------------- 1 | import { IPeriod } from "@/models/period" 2 | import { IStore } from "@/models/store" 3 | import { APIHandler } from "@/util/apiHandler" 4 | import { Ref } from "@vue/composition-api" 5 | import { AxiosResponse } from "axios" 6 | import VueRouter from "vue-router" 7 | import { Store } from "vuex/types/index" 8 | 9 | 10 | class PeriodAPIHandler extends APIHandler{ 11 | 12 | constructor( store: Store, router: VueRouter ) { 13 | super(store, router) 14 | } 15 | 16 | GetPeriods(isLoading: Ref, 17 | SuccessFunc?: (response: AxiosResponse) => void, 18 | ErrorFunc?: (error: any) => void){ 19 | this.HttpPost('api/Period/GetPeriods', isLoading, undefined, SuccessFunc, ErrorFunc) 20 | } 21 | 22 | CreatePeriods(data: Array, isLoading: Ref, 23 | SuccessFunc?: (response: AxiosResponse) => void, 24 | ErrorFunc?: (error: any) => void){ 25 | this.HttpPost('api/Period/CreatePeriods', isLoading, data, SuccessFunc, ErrorFunc) 26 | } 27 | 28 | UpdatePeriods(data: Array, isLoading: Ref, 29 | SuccessFunc?: (response: AxiosResponse) => void, 30 | ErrorFunc?: (error: any) => void){ 31 | this.HttpPost('api/Period/UpdatePeriods', isLoading, data, SuccessFunc, ErrorFunc) 32 | } 33 | 34 | DeletePeriods(data: Array, isLoading: Ref, 35 | SuccessFunc?: (response: AxiosResponse) => void, 36 | ErrorFunc?: (error: any) => void){ 37 | this.HttpPost('api/Period/DeletePeriods', isLoading, data, SuccessFunc, ErrorFunc) 38 | } 39 | } 40 | 41 | export { 42 | PeriodAPIHandler 43 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/api/taskReporter.ts: -------------------------------------------------------------------------------- 1 | import { IStore } from "@/models/store" 2 | import { IQueryPeopleTasks } from "@/models/tasks" 3 | import { APIHandler } from "@/util/apiHandler" 4 | import { Ref } from "@vue/composition-api" 5 | import { AxiosResponse } from "axios" 6 | import VueRouter from "vue-router" 7 | import { Store } from "vuex/types/index" 8 | 9 | 10 | class TaskReporterAPIHandler extends APIHandler{ 11 | 12 | constructor( store: Store, router: VueRouter ) { 13 | super(store, router) 14 | } 15 | 16 | GetSimpleSummary(data: IQueryPeopleTasks, isLoading: Ref, 17 | SuccessFunc?: (response: AxiosResponse) => void, 18 | ErrorFunc?: (error: any) => void){ 19 | this.HttpPost('api/TaskReporter/GetSimpleSummary', isLoading, data, SuccessFunc, ErrorFunc) 20 | } 21 | 22 | GetTaskTimeSummaryDetail(data: IQueryPeopleTasks, isLoading: Ref, 23 | SuccessFunc?: (response: AxiosResponse) => void, 24 | ErrorFunc?: (error: any) => void){ 25 | this.HttpPost('api/TaskReporter/GetTaskTimeSummaryDetail', isLoading, data, SuccessFunc, ErrorFunc) 26 | } 27 | 28 | GetTaskTypeSummary(data: IQueryPeopleTasks, isLoading: Ref, 29 | SuccessFunc?: (response: AxiosResponse) => void, 30 | ErrorFunc?: (error: any) => void){ 31 | this.HttpPost('api/TaskReporter/GetTaskTypeSummary', isLoading, data, SuccessFunc, ErrorFunc) 32 | } 33 | 34 | GetTaskSourceSummary(data: IQueryPeopleTasks, isLoading: Ref, 35 | SuccessFunc?: (response: AxiosResponse) => void, 36 | ErrorFunc?: (error: any) => void){ 37 | this.HttpPost('api/TaskReporter/GetTaskSourceSummary', isLoading, data, SuccessFunc, ErrorFunc) 38 | } 39 | 40 | GetTaskTimeSummary(data: IQueryPeopleTasks, isLoading: Ref, 41 | SuccessFunc?: (response: AxiosResponse) => void, 42 | ErrorFunc?: (error: any) => void){ 43 | this.HttpPost('api/TaskReporter/GetTaskTimeSummary', isLoading, data, SuccessFunc, ErrorFunc) 44 | } 45 | 46 | } 47 | 48 | 49 | export { 50 | TaskReporterAPIHandler 51 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/always_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/always_grey.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_1.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_2.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_3.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/blue_wash_wall_4.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/crossword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/crossword.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/dark_wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/dark_wood.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/dust_scratches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/dust_scratches.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/fabric_of_squares_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/fabric_of_squares_gray.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/footer_lodyas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/footer_lodyas.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/footer_lodyas2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/footer_lodyas2.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/forest/forest1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/forest/forest1.jpg -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/forest/forest2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/forest/forest2.jpg -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/grey_wash_wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/grey_wash_wall.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/noisy_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/noisy_grid.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/noisy_grid2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/noisy_grid2.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/pikachou.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/pikachou.gif -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/project_papper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/project_papper.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/triangular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/triangular.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/vichy-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/vichy-dark.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/vichy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/vichy.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/whitey copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/whitey copy.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/whitey-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/whitey-light.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/whitey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/whitey.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/xv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/xv.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/img/xv_light_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/img/xv_light_blue.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/TimeTracker/TimeTracker/ClientApp/src/assets/logo.png -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/auth/SignInModal.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 53 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/card/GeneralCard.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 61 | 62 | 65 | 66 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/clock/AnalogClock.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 44 | 45 | 50 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/clock/Clock.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 66 | 67 | 75 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/clock/DigitalClock.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 58 | 59 | 97 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/img/SimpleImageEditor.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 71 | 72 | 94 | 95 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/nav/NavGithubLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/nav/NavIndividualSettings.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/nav/NavItem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 30 | 31 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/nav/NavLinkItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 60 | 61 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/nav/NavProgress.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 25 | 26 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/nav/TwoColumnSidebarToggleIcon.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 40 | 41 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/trackTask/taskForm/TaskDayLeaveCheckbox.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 54 | 55 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/trackTask/taskForm/TaskDayLeaveIcon.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 57 | 58 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/trackTask/taskForm/taskform.ts: -------------------------------------------------------------------------------- 1 | 2 | interface IAddRow{ 3 | newIndex: number; 4 | } 5 | 6 | interface IRemoveRow{ 7 | oldIndex: number; 8 | value: any; 9 | } 10 | 11 | interface IMoveRow{ 12 | newIndex: number; 13 | oldIndex: number; 14 | } 15 | 16 | interface IUpdateCell{ 17 | rowIndex: number; 18 | relatedKey: string; 19 | oldValue: any; 20 | newValue: any; 21 | } 22 | 23 | export { 24 | IAddRow, 25 | IRemoveRow, 26 | IMoveRow, 27 | IUpdateCell, 28 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/components/trackTask/toolbar/DateRangeSelector.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 56 | 57 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/configGtag.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { VueConstructor } from 'vue' 4 | import VueGtag from "vue-gtag" 5 | import VueRouter from 'vue-router' 6 | 7 | function ConfigGtag(vue: VueConstructor, router: VueRouter) { 8 | const gaId = process.env.VUE_APP_GA_ID 9 | // console.log("check gtag", gaId) 10 | if (process.env.NODE_ENV === 'production' && gaId ) { 11 | vue.use(VueGtag, { 12 | config: { 13 | id: gaId 14 | }, 15 | appName: 'Time Tracker', 16 | pageTrackerScreenviewEnabled: true, 17 | }, router) 18 | } 19 | } 20 | 21 | export default ConfigGtag -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/jctk-table-form.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jctk-table-form/src/components/ShareVar'{ 2 | interface ICellTypes{ 3 | number: string; 4 | singleSelect: string; 5 | textarea: string; 6 | } 7 | 8 | interface IFormSettings{ 9 | cellTypes: ICellTypes; 10 | } 11 | 12 | declare let FormSettings:IFormSettings 13 | 14 | export default FormSettings 15 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './registerServiceWorker' 4 | import CreateRouter from './router/index' 5 | import store from './store' 6 | import VueCompositionAPI from '@vue/composition-api' 7 | import ConfigGtag from './configGtag' 8 | 9 | import vuetify from './vuetify' 10 | 11 | import 'jctk-table-form' 12 | import 'jctk-table-form/dist/jctk-table-form.css' 13 | 14 | // for icon 15 | import 'material-design-icons/iconfont/material-icons.css' 16 | import '@mdi/font/css/materialdesignicons.css' 17 | 18 | import 'animate.css/animate.min.css' 19 | 20 | Vue.config.productionTip = false 21 | 22 | Vue.use(VueCompositionAPI) 23 | 24 | const router = CreateRouter(store) 25 | 26 | ConfigGtag( Vue, router) 27 | 28 | new Vue({ 29 | vuetify, 30 | router, 31 | store, 32 | render: h => h(App) 33 | }).$mount('#app') 34 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/authentication.ts: -------------------------------------------------------------------------------- 1 | import { AccountStatus } from './constants/authentication' 2 | 3 | interface IUserRole{ 4 | id: number; 5 | codeName: string; 6 | displayName: string; 7 | } 8 | 9 | interface IClaims{ 10 | guid: string; 11 | name: string; 12 | email: string; 13 | accountStatus: AccountStatus; 14 | userRoles: Array; 15 | } 16 | 17 | interface IAuthentication{ 18 | isAuthenticated: boolean; 19 | claims: IClaims; 20 | } 21 | 22 | interface ILogin{ 23 | email: string; 24 | name: string; 25 | password: string; 26 | } 27 | 28 | interface IUpdatePassword{ 29 | currentPassword: string; 30 | password: string; 31 | } 32 | 33 | interface IUpdateAccounts{ 34 | Guid: string; 35 | Name: string; 36 | IsUpdateName: boolean; 37 | 38 | AccountStatus: AccountStatus; 39 | IsUpdateAccountStatus: boolean; 40 | 41 | UserRoles: Array; 42 | IsUpdateUserRoles: boolean; 43 | } 44 | 45 | enum ValidationResults{ 46 | ok, 47 | invalidPath, 48 | invalidAccountStatus, 49 | invalidAuthentication, 50 | invalidRole, 51 | logout, 52 | } 53 | 54 | function GetAccountRemind(accountStatus: AccountStatus){ 55 | switch (accountStatus) { 56 | case AccountStatus.Uncheck: 57 | return "Please connect admin to activate your account." 58 | case AccountStatus.Approved: 59 | return "" 60 | case AccountStatus.Rejected: 61 | return "Your account is rejected." 62 | case AccountStatus.Suspend: 63 | return "Your account is suspended." 64 | default: 65 | return "" 66 | } 67 | } 68 | 69 | export { 70 | IUserRole, 71 | IClaims, 72 | IAuthentication, 73 | ILogin, 74 | IUpdatePassword, 75 | IUpdateAccounts, 76 | ValidationResults, 77 | GetAccountRemind, 78 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/charts.ts: -------------------------------------------------------------------------------- 1 | interface IDayCount{ 2 | date: string; 3 | consumeTime: number; 4 | overtime: number; 5 | isLeave: boolean; 6 | } 7 | 8 | interface IEchartsPieRow{ 9 | name: string; 10 | value: number; 11 | } 12 | 13 | interface ITaskTimeSummaryDetail{ 14 | date: string; 15 | totalConsumeTime: number; 16 | totalOvertime: number; 17 | totalLeave: number; 18 | avgConsumeTime: number; 19 | avgOvertime: number; 20 | medianConsumeTime: number; 21 | medianOvertime: number; 22 | minConsumeTime: number; 23 | minOvertime: number; 24 | maxConsumeTime: number; 25 | maxOvertime: number; 26 | } 27 | 28 | export { 29 | IDayCount, 30 | IEchartsPieRow, 31 | ITaskTimeSummaryDetail, 32 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/constants/authentication.ts: -------------------------------------------------------------------------------- 1 | enum AccountStatus{ 2 | Uncheck = 0, // 未審核用戶 3 | Approved = 1, // 合法用戶 4 | Rejected = 2, // 拒絕用戶 5 | Suspend = 3, // 停權用戶 6 | } 7 | 8 | enum UserRoles 9 | { 10 | Admin = 1, 11 | User = 2, 12 | } 13 | 14 | const accountStatusIcon = { 15 | 0: "mdi-account", 16 | 1: "mdi-account", 17 | 2: "mdi-account", 18 | 3: "mdi-account", 19 | } 20 | 21 | const accountStatusColor = { 22 | 0: "warning", 23 | 1: "primary", 24 | 2: "error", 25 | 3: "error", 26 | } 27 | 28 | function GetAccountStatusKey(){ 29 | return Object.keys(AccountStatus).map(Number).filter( x => !Number.isNaN(x)) 30 | } 31 | 32 | function GetUserRolesKey(){ 33 | return Object.keys(UserRoles).map(Number).filter( x => !Number.isNaN(x)) 34 | } 35 | 36 | export { 37 | accountStatusIcon, 38 | accountStatusColor, 39 | AccountStatus, 40 | UserRoles, 41 | GetAccountStatusKey, 42 | GetUserRolesKey, 43 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/constants/task.ts: -------------------------------------------------------------------------------- 1 | import FormSettings from 'jctk-table-form/src/components/ShareVar' 2 | 3 | import { ITaskOption, ITaskParameters } from "../tasks"; 4 | 5 | const TransformedOptions = ( taskOptions: Array ) => 6 | taskOptions.map( taskOption => ({ name: taskOption.displayName, value: taskOption.id }) ) || [] 7 | 8 | const GetOptions = ( taskParameters: ITaskParameters ) =>({ 9 | head:[{ 10 | title: "時數", 11 | relatedKey: "consumeTime", 12 | cellType: FormSettings.cellTypes.number /*as string*/, 13 | style:{ 14 | "width": "80px", 15 | "min-width": "80px", 16 | }, 17 | options:{ 18 | step: 0.5, 19 | min: 0.0, 20 | max: 12.0, 21 | }, 22 | },{ 23 | title: "工作類型", 24 | relatedKey: "taskType", 25 | cellType: FormSettings.cellTypes.singleSelect /*as string*/, 26 | style:{ 27 | width: "160px", 28 | "min-width": "160px", 29 | }, 30 | options: TransformedOptions(taskParameters.taskTypes), 31 | },{ 32 | title: "工作來源", 33 | relatedKey: "taskSource", 34 | cellType: FormSettings.cellTypes.singleSelect /*as string*/, 35 | style:{ 36 | width: "160px", 37 | "min-width": "160px", 38 | }, 39 | options: TransformedOptions(taskParameters.taskSources), 40 | },{ 41 | title: "工作名稱", 42 | relatedKey: "taskName", 43 | cellType: FormSettings.cellTypes.textarea /*as string*/, 44 | options:{ 45 | maxLength: 256, 46 | isSuggestions: true, 47 | throttle: 250, 48 | }, 49 | },{ 50 | title: "工作內容", 51 | relatedKey: "taskContent", 52 | cellType: FormSettings.cellTypes.textarea /*as string*/, 53 | options:{ 54 | maxLength: 256, 55 | isSuggestions: true, 56 | throttle: 250, 57 | }, 58 | }] 59 | }) 60 | 61 | export { 62 | GetOptions 63 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/constants/webSocket.ts: -------------------------------------------------------------------------------- 1 | enum WSMapCode{ 2 | GetUserInfo = 0, 3 | TaskEditorCreateTask = 1, 4 | TaskEditorDeleteTasks = 2, 5 | TaskEditorUpdateTaskRowOrder = 3, 6 | TaskEditorUpdateTaskCol = 4, 7 | TaskEditorUpdateIsLeave = 5, 8 | } 9 | 10 | export { 11 | WSMapCode 12 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/nav.ts: -------------------------------------------------------------------------------- 1 | import { routeConfigs } from '@/router/routeConfigs' 2 | import { ValidateAuth } from '@/util/authValidation' 3 | import { ValidationResults } from '@/models/authentication' 4 | import { Store } from 'vuex/types/index' 5 | import { IStore } from './store' 6 | 7 | const allNavLeftItems = [ "TaskEditor", "TaskReporter", "TaskSummaryReporter" ] 8 | const allNavRightItems = [ "TrackSettings", "PermissionEditor" ] 9 | 10 | function FiltNavItems(candidates: Array, store: Store): Array{ 11 | return candidates.filter( 12 | pathName => ValidateAuth(pathName as string, store, routeConfigs) == ValidationResults.ok) 13 | } 14 | 15 | export { 16 | allNavLeftItems, 17 | allNavRightItems, 18 | 19 | FiltNavItems, 20 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/notification.ts: -------------------------------------------------------------------------------- 1 | import ToastInterface from 'vue-toastification/dist/types/src/ts/interface'; 2 | 3 | interface INotification{ 4 | Notificate403: any; 5 | Notificate401: any; 6 | NotificateWSReconnected: any; 7 | NotificateWSReconnecting: any; 8 | NotificateWSClose: any; 9 | notificator: ReturnType | undefined, 10 | } 11 | 12 | 13 | export { 14 | INotification 15 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/pageIdle.ts: -------------------------------------------------------------------------------- 1 | enum IdleDetermineStates{ 2 | ByPass, 3 | PageIdle, 4 | UserComfirm, 5 | Logout, 6 | } 7 | 8 | interface IPageIdle{ 9 | startIdleTimestamp?: Date; 10 | startUserConfirmTimestamp?: Date; 11 | isShowUserConfirm: boolean; 12 | isShowLogOutNotification: boolean; 13 | idleTimes: number; 14 | idleDetermineStates: IdleDetermineStates; 15 | } 16 | 17 | export { 18 | IdleDetermineStates, 19 | IPageIdle 20 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/parameter.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from "@vue/composition-api" 2 | import { AxiosResponse } from "axios" 3 | 4 | interface ITaskEntityOption{ 5 | guid: string; 6 | codeName: string; // unique 7 | displayName: string; 8 | isActive: boolean; 9 | } 10 | 11 | interface IOptionMethods{ 12 | GetOptions: ( 13 | isLoading: Ref, 14 | SuccessFunc?: (response: AxiosResponse) => void, 15 | ErrorFunc?: (error: any) => void 16 | ) => void; 17 | CreateOptions: ( 18 | data: Array, 19 | isLoading: Ref, 20 | SuccessFunc?: (response: AxiosResponse) => void, 21 | ErrorFunc?: (error: any) => void 22 | ) => void 23 | UpdateOptions: ( 24 | data: Array, 25 | isLoading: Ref, 26 | SuccessFunc?: (response: AxiosResponse) => void, 27 | ErrorFunc?: (error: any) => void 28 | ) => void 29 | } 30 | 31 | export { 32 | ITaskEntityOption, 33 | IOptionMethods, 34 | } 35 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/period.ts: -------------------------------------------------------------------------------- 1 | 2 | interface IPeriod{ 3 | guid: string; 4 | startDate: string; 5 | endDate: string; 6 | name: string; 7 | } 8 | 9 | export { 10 | IPeriod 11 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/routeConfigs.ts: -------------------------------------------------------------------------------- 1 | import { VueConstructor } from "vue"; 2 | 3 | import { UserRoles } from '@/models/constants/authentication' 4 | 5 | interface IRouteConfigs{ 6 | [key: string]: IRouteConfig; 7 | } 8 | 9 | interface IRouteConfig{ 10 | path: string; 11 | name: string; 12 | component: VueConstructor | ( () => Promise ); 13 | auth: IRouteAuth; 14 | nav: IRouteNav; 15 | } 16 | 17 | interface IRouteAuth{ 18 | isRequiredAuthentication: boolean; 19 | isRequiredApprovedAccount: boolean; 20 | requiredRoles: Array; 21 | } 22 | 23 | interface IRouteNav{ 24 | displayName: string; 25 | mdiIcon: string; 26 | } 27 | 28 | export { 29 | IRouteConfigs, 30 | IRouteConfig, 31 | IRouteAuth, 32 | IRouteNav, 33 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/store.ts: -------------------------------------------------------------------------------- 1 | import { IAuthentication } from './authentication' 2 | import { INotification } from './notification' 3 | import { IPageIdle } from './pageIdle' 4 | import { ITaskParameters } from './tasks' 5 | import { ITwoColumn } from './twoColumn' 6 | import { IWebSocket } from './webSocket' 7 | 8 | 9 | interface IStoreRoot{ 10 | isLoading: boolean, 11 | tabGuid: string, 12 | userImage: string, 13 | } 14 | 15 | interface IStore extends IStoreRoot{ 16 | authentication: IAuthentication, 17 | pageIdle: IPageIdle, 18 | taskParameters: ITaskParameters, 19 | notification: INotification, 20 | webSocket: IWebSocket, 21 | twoColumn: ITwoColumn, 22 | } 23 | 24 | export { 25 | IStoreRoot, 26 | IStore, 27 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/tasks.ts: -------------------------------------------------------------------------------- 1 | 2 | interface IDateRange{ 3 | periodName: string; 4 | isUsedPeriod: boolean; 5 | startDate: string; 6 | endDate: string; 7 | } 8 | 9 | interface ITaskOption{ 10 | id: number; 11 | guid: string; 12 | codeName: string; 13 | displayName: string; 14 | } 15 | 16 | interface ITaskParameters{ 17 | taskTypes: Array; 18 | taskSources: Array; 19 | dayWorkLimitTime: number | undefined; 20 | } 21 | 22 | interface ITask{ 23 | guid: string; 24 | displayOrder: number; 25 | date: string; 26 | consumeTime: number; 27 | taskType: number; 28 | taskSource: number; 29 | taskName: string; 30 | taskContent: string; 31 | } 32 | 33 | interface IDeleteTasks{ 34 | ownerGuid: string; 35 | date: string; 36 | taskGuids: Array; 37 | } 38 | 39 | interface IUpdateIsLeave{ 40 | ownerGuid: string; 41 | date: string; 42 | isLeave: boolean; 43 | } 44 | 45 | interface IUpdateTask{ 46 | guid: string; 47 | displayOrder: number; 48 | date: string; 49 | consumeTime: number; 50 | taskType: ITaskOption; 51 | taskSource: ITaskOption; 52 | taskName: string; 53 | taskContent: string; 54 | } 55 | 56 | interface IUpdateTaskRowOrder{ 57 | ownerGuid: string; 58 | guid: string; // task guid 59 | date: string; 60 | displayOrder: number; 61 | } 62 | 63 | interface IUpdateTaskCol{ 64 | ownerGuid: string; 65 | guid: string; // task guid 66 | date: string; 67 | relatedKey: string; 68 | value: any; 69 | } 70 | 71 | interface IGetDaysDataResponseTask{ 72 | guid: string; 73 | displayOrder: number; 74 | date: string; 75 | consumeTime: number; 76 | taskType: ITaskOption | number; 77 | taskSource: ITaskOption | number; 78 | taskName: string; 79 | taskContent: string; 80 | } 81 | 82 | interface IDayData{ 83 | guid: string; 84 | date: string; 85 | isLeave: boolean; 86 | isFormClicked: boolean; 87 | formData: Array; 88 | } 89 | 90 | interface IGetDaysDataResponse{ 91 | guid: string; 92 | date: string; 93 | isLeave: boolean; 94 | isFormClicked: boolean; 95 | formData: Array; 96 | } 97 | 98 | interface IQueryTasks{ 99 | ownerGuid: string; 100 | startDate: string; 101 | endDate: string; 102 | } 103 | 104 | interface IQueryPeopleTasks{ 105 | ownerGuids: Array; 106 | startDate: string; 107 | endDate: string; 108 | } 109 | 110 | interface ICreateTask{ 111 | tasks: Array; 112 | ownerGuid: string; 113 | } 114 | 115 | export { 116 | IDateRange, 117 | ITaskOption, 118 | ITaskParameters, 119 | ITask, 120 | IDeleteTasks, 121 | IUpdateIsLeave, 122 | IUpdateTask, 123 | IUpdateTaskRowOrder, 124 | IUpdateTaskCol, 125 | IDayData, 126 | IGetDaysDataResponse, 127 | IQueryTasks, 128 | IQueryPeopleTasks, 129 | ICreateTask, 130 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/twoColumn.ts: -------------------------------------------------------------------------------- 1 | interface ITwoColumn{ 2 | isShowSidebar: boolean; 3 | isActiveTwoColumn: boolean; 4 | } 5 | 6 | export { 7 | ITwoColumn 8 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/models/webSocket.ts: -------------------------------------------------------------------------------- 1 | import { WSHandler } from '@/api/webSocket'; 2 | import { IQueryTasks } from './tasks'; 3 | 4 | interface IWebSocket{ 5 | wsHandler: WSHandler | undefined; 6 | taskEditorQueryTasks: IQueryTasks | undefined; 7 | } 8 | 9 | export { 10 | IWebSocket 11 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready () { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered () { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached () { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound () { 20 | console.log('New content is downloading.') 21 | }, 22 | updated () { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline () { 26 | console.log('No internet connection found. App is running in offline mode.') 27 | }, 28 | error (error) { 29 | console.error('Error during service worker registration:', error) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter, { RouteConfig } from 'vue-router' 3 | import { Store } from 'vuex/types/index' 4 | import { routeConfigs } from './routeConfigs' 5 | import ConfigRouteValidation from './routeRoleValidation' 6 | 7 | Vue.use(VueRouter) 8 | 9 | const routes: Array = Object.values(routeConfigs).map( routeConfig => ({ 10 | path: routeConfig.path, 11 | name: routeConfig.name, 12 | component: routeConfig.component, 13 | })) 14 | 15 | export default function(store: Store): VueRouter{ 16 | const router = new VueRouter({ 17 | mode: 'history', 18 | base: process.env.BASE_URL, 19 | routes 20 | }) 21 | 22 | ConfigRouteValidation(router, store) 23 | 24 | return router 25 | } 26 | 27 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/router/routeRoleValidation.ts: -------------------------------------------------------------------------------- 1 | import { VueRouter, Route } from 'vue-router/types/router' 2 | import { Store } from 'vuex/types/index' 3 | 4 | import { IStore } from '@/models/store' 5 | import { ValidateAuth } from '@/util/authValidation' 6 | import { ValidationResults } from '@/models/authentication' 7 | import { GetRedirectPath, routeConfigs } from './routeConfigs' 8 | 9 | import { ToastWarning } from '@/util/notification' 10 | 11 | function ConfigRouteValidation(router: VueRouter, store: Store ){ 12 | router.beforeEach((to, from, next) => { 13 | if (from.path == to.path) { 14 | next() 15 | return 16 | } 17 | 18 | const result = ValidateAuth(to.name as string, store, routeConfigs) 19 | if( result != ValidationResults.ok ){ 20 | // setTimeout( () => ToastWarning(store.state.notificator, `Insufficient permision found: ${ValidationResults[result]}`), 2000 ) 21 | ToastWarning(`Insufficient permision found: ${ValidationResults[result]}`, store.state.notification.notificator) 22 | next(GetRedirectPath(result)) 23 | return 24 | } 25 | 26 | next() 27 | }) 28 | 29 | function HasQueryParams(route: Route) { 30 | return !!Object.keys(route.query).length 31 | } 32 | 33 | router.beforeEach((to, from, next) => { 34 | if( !HasQueryParams(to) && HasQueryParams(from) ){ 35 | next({ name: to.name as string, query: from.query }) 36 | } else { 37 | next() 38 | } 39 | }) 40 | } 41 | 42 | export default ConfigRouteValidation -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/scss/example.scss: -------------------------------------------------------------------------------- 1 | // ================================================================================= 2 | // midea query 3 | // ================================================================================= 4 | @media (max-width: $mediaquery-width-sm) { 5 | 6 | } 7 | 8 | // Small devices (landscape phones, 576px and up) 9 | @media (min-width: $mediaquery-width-sm) and (max-width: $mediaquery-width-md) { 10 | 11 | } 12 | 13 | // Medium devices (tablets, 768px and up) 14 | @media (min-width: $mediaquery-width-md) and (max-width: $mediaquery-width-l) { 15 | 16 | } 17 | 18 | // Large devices (desktops, 992px and up) 19 | @media (min-width: $mediaquery-width-l) and (max-width: $mediaquery-width-xl) { 20 | 21 | } 22 | 23 | // Extra large devices (large desktops, 1200px and up) 24 | @media (min-width: $mediaquery-width-xl) { 25 | 26 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/scss/global_var.scss: -------------------------------------------------------------------------------- 1 | @import "./var"; 2 | 3 | @include vm-global(); 4 | 5 | hr.vm-test{ 6 | margin: 0 25px; 7 | height: 500px; 8 | width: 1px; 9 | background: black; 10 | background: -webkit-gradient(linear, 180 180, 100% 0, from(white), to(white), color-stop(50%, black)); 11 | } 12 | 13 | $alert-color: #922828; 14 | 15 | $tmp-color: rgba(165, 126, 126, 0.459); 16 | $tmp-color: #e6dcdc; 17 | 18 | $tmp-color: #922828; 19 | $tmp-color: #e27c7c; 20 | 21 | $tmp-color: #2c3e50; 22 | $tmp-color: #496586; 23 | $tmp-color: #f5f3f3; 24 | 25 | .flex-center{ 26 | display: flex; 27 | justify-content: center; 28 | } 29 | 30 | .margin-center{ 31 | margin: 0 auto; 32 | } 33 | 34 | $color-primary: #009688; 35 | $color-secondary: #00bcd4; 36 | $color-accent: #607d8b; 37 | $color-error: #ff5722; 38 | $color-warning: #ff9800; 39 | $color-info: #2196f3; 40 | $color-success: #4caf50; 41 | $color-title: #334b5c; 42 | 43 | .theme-table.theme--light.v-data-table tbody tr.v-data-table__selected{ 44 | background: $color-accent; 45 | color: #d3d3d3; 46 | 47 | &:hover{ 48 | background: $color-primary !important; 49 | color: #d3d3d3 !important; 50 | } 51 | } 52 | 53 | html { 54 | // background: $background; 55 | // background: red; 56 | // background-attachment: fixed; 57 | 58 | overflow: auto; 59 | overflow-y: auto !important; 60 | 61 | // @media (max-width: $mediaquery-width-sm) { 62 | // &::-webkit-scrollbar { 63 | // display: none; 64 | // } 65 | // } 66 | 67 | // @media (min-width: $mediaquery-width-sm) { 68 | &::-webkit-scrollbar 69 | { 70 | width: 14px; 71 | height: 12px; 72 | background-color: #eaeaea; 73 | } 74 | 75 | &::-webkit-scrollbar-thumb 76 | { 77 | border-radius: 10px; 78 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 79 | background-color: #d8d8d8; 80 | 81 | &:hover{ 82 | background-color: #ecebeb; 83 | } 84 | } 85 | // } 86 | } 87 | 88 | .animate__animated.animate__fastest { 89 | -webkit-animation-duration: 0.25s; 90 | animation-duration: 0.25s; 91 | -webkit-animation-duration: 0.25s; 92 | animation-duration: 0.25s; 93 | } 94 | 95 | .user-image-nav{ 96 | height: 48px; 97 | width: 48px; 98 | border-radius: 48px; 99 | } 100 | 101 | .user-image-big{ 102 | height: 256px; 103 | width: 256px; 104 | border-radius: 256px; 105 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/store/authentication.ts: -------------------------------------------------------------------------------- 1 | import { IAuthentication, IClaims } from '@/models/authentication' 2 | import { Module } from "vuex"; 3 | 4 | const GetInitAuthentication = (): IAuthentication => ({ 5 | isAuthenticated: false, 6 | claims: { 7 | guid: "", 8 | email: "", 9 | name: "", 10 | accountStatus: 0, 11 | userRoles: [], 12 | }, 13 | }) 14 | 15 | function SetClaims( state: IAuthentication, claims: IClaims ){ 16 | Object.keys(claims).forEach( key => Reflect.set( state.claims, key, Reflect.get(claims, key) ) ) 17 | } 18 | 19 | const module: Module = { 20 | namespaced: true, 21 | state: GetInitAuthentication(), 22 | mutations: { 23 | Init: ( state ) => { 24 | const initAuthentication = GetInitAuthentication() 25 | Object.keys(initAuthentication).forEach( key => Reflect.set( state, key, Reflect.get(initAuthentication, key) ) ) 26 | SetClaims(state, state.claims) 27 | }, 28 | SetIsAuthenticated: ( state, isAuthenticated: boolean ) => state.isAuthenticated = isAuthenticated, 29 | SetClaims: ( state, claims: IClaims ) => SetClaims(state, claims), 30 | SetEmail: ( state, email: string ) => state.claims.email = email, 31 | SetName: ( state, name: string ) => state.claims.name = name, 32 | }, 33 | actions: { 34 | }, 35 | modules: { 36 | } 37 | } 38 | 39 | export default module -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex, { Store } from 'vuex' 3 | 4 | import createMultiTabState from 'vuex-multi-tab-state' 5 | 6 | import { IStore, IStoreRoot } from '@/models/store' 7 | import authentication from './authentication' 8 | import pageIdle from './pageIdle' 9 | import taskParameters from './taskParameters' 10 | import notification from './notification' 11 | import webSocket from './webSocket' 12 | import twoColumn from './twoColumn' 13 | import { GetGuid } from '@/util/authentication' 14 | 15 | Vue.use(Vuex) 16 | 17 | export default new Vuex.Store({ 18 | state: { 19 | isLoading: false, 20 | tabGuid: GetGuid(), 21 | userImage: "", 22 | }, 23 | mutations: { 24 | TurnOnLoading: ( state ) => { 25 | state.isLoading = true 26 | }, 27 | TurnOffLoading: ( state ) => { 28 | state.isLoading = false 29 | }, 30 | SetUserImage: ( state, userImage: string ) => state.userImage = userImage 31 | }, 32 | actions: { 33 | }, 34 | modules: { 35 | taskParameters, 36 | authentication, 37 | pageIdle, 38 | notification, 39 | webSocket, 40 | twoColumn, 41 | }, 42 | plugins: [ 43 | createMultiTabState({ 44 | statesPaths: ['authentication', 'pageIdle', 'userImage'] 45 | }), 46 | ], 47 | }) as Store 48 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/store/notification.ts: -------------------------------------------------------------------------------- 1 | import { INotification } from '@/models/notification'; 2 | import { ToastSuccess, ToastWarning } from '@/util/notification'; 3 | import debounce from 'lodash.debounce'; 4 | import Vue from 'vue' 5 | import { TYPE } from 'vue-toastification/dist/types/src/ts/constants'; 6 | import ToastInterface from 'vue-toastification/dist/types/src/ts/interface'; 7 | import { ToastOptions } from 'vue-toastification/dist/types/src/types'; 8 | import { Module } from "vuex"; 9 | 10 | const GetInitState = (): INotification => ({ 11 | notificator: undefined, 12 | Notificate401: undefined, 13 | Notificate403: undefined, 14 | NotificateWSReconnected: undefined, 15 | NotificateWSReconnecting: undefined, 16 | NotificateWSClose: undefined, 17 | }) 18 | 19 | const GetDebounceNoter = ( state: INotification, message: string, debouncTime = 1000, option?: (ToastOptions & { 20 | type?: TYPE.WARNING | undefined;}) | undefined) => 21 | debounce( () => ToastWarning( message, state.notificator, option ), debouncTime ) 22 | 23 | const SetNotificate401 = (state: INotification) => 24 | state.Notificate401 = GetDebounceNoter( state, 25 | `Sorry! You don't have sufficient permission. You will be signed out automatically.`) 26 | 27 | const SetNotificate403 = (state: INotification) => 28 | state.Notificate403 = GetDebounceNoter( state, 29 | `Sorry! You don't have sufficient permission.`) 30 | 31 | const SetNotificateWSReconnected = (state: INotification) => 32 | state.NotificateWSReconnected = debounce( () => ToastSuccess( 33 | `The connection is restored successfully.`, state.notificator ), 500 ) 34 | 35 | const SetNotificateWSReconnecting = (state: INotification) => 36 | state.NotificateWSReconnecting = GetDebounceNoter( state, 37 | `Sorry! The connection is unstable now. Please wait for the connection.`, 500) 38 | 39 | const SetNotificateWSClose = (state: INotification) => 40 | state.NotificateWSClose = GetDebounceNoter( state, 41 | `Sorry! The connection is break unpredictably. Your operation will not be executing. Please refresh the browser to continue.`, 500, { timeout: false }) 42 | 43 | const module: Module = { 44 | namespaced: true, 45 | state: GetInitState(), 46 | mutations: { 47 | Init: ( state ) => { 48 | const initState = GetInitState() 49 | Vue.set(state, "notificator", initState.notificator) 50 | }, 51 | SetNotificator: ( state, notificator: ReturnType ) => { 52 | state.notificator = notificator 53 | SetNotificate401( state ) 54 | SetNotificate403( state ) 55 | SetNotificateWSReconnected( state ) 56 | SetNotificateWSReconnecting( state ) 57 | SetNotificateWSClose( state ) 58 | }, 59 | }, 60 | actions: { 61 | }, 62 | modules: { 63 | } 64 | } 65 | 66 | export default module -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/store/pageIdle.ts: -------------------------------------------------------------------------------- 1 | import { IdleDetermineStates, IPageIdle } from '@/models/pageIdle' 2 | import { Module } from 'vuex/types/index' 3 | 4 | const GetInitPageIdle = (): IPageIdle => ({ 5 | startIdleTimestamp: undefined, 6 | startUserConfirmTimestamp: undefined, 7 | isShowUserConfirm: false, 8 | isShowLogOutNotification: false, 9 | idleTimes: 0, 10 | idleDetermineStates: IdleDetermineStates.ByPass, 11 | }) 12 | 13 | const module: Module = { 14 | namespaced: true, 15 | state: GetInitPageIdle(), 16 | getters:{ 17 | }, 18 | mutations: { 19 | Init: (state) => { 20 | const initPageIdle = GetInitPageIdle() 21 | Object.keys(initPageIdle).forEach( key => Reflect.set( state, key, Reflect.get(initPageIdle, key) ) ) 22 | }, 23 | SetStartIdleTimestamp: (state, timeStamp: Date) => state.startIdleTimestamp = timeStamp, 24 | SetStartUserConfirmTimestamp: (state, timeStamp: Date) => state.startUserConfirmTimestamp = timeStamp, 25 | SetIsShowUserConfirm: (state, isShowUserConfirm: boolean) => state.isShowUserConfirm = isShowUserConfirm, 26 | SetIsShowLogOutNotification: (state, isShowLogOutNotification: boolean) => state.isShowLogOutNotification = isShowLogOutNotification, 27 | SetIdleTimes: (state, idleTimes: number) => state.idleTimes = idleTimes, 28 | SetIdleDetermineStates: ( state, idleDetermineStates: IdleDetermineStates ) => state.idleDetermineStates = idleDetermineStates, 29 | }, 30 | actions: { 31 | }, 32 | modules: { 33 | } 34 | } 35 | 36 | export default module -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/store/taskParameters.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { ITaskOption, ITaskParameters } from '@/models/tasks'; 3 | import { Module } from "vuex"; 4 | 5 | const TASKTYPES = "taskTypes" 6 | const TASKSOURCES = "taskSources" 7 | const DAYWORKLIMITTIME = "dayWorkLimitTime" 8 | 9 | const GetInitState = (): ITaskParameters => ({ 10 | taskTypes: [], 11 | taskSources: [], 12 | dayWorkLimitTime: undefined, 13 | }) 14 | 15 | const SetOptions = ( state: ITaskParameters, tasktype: string, taskOptions: Array ) => { 16 | taskOptions.sort( ( x, y ) => { 17 | if (x.displayName < y.displayName) { 18 | return -1 19 | } 20 | if (x.displayName > y.displayName) { 21 | return 1 22 | } 23 | return 0 24 | }).forEach( (option, idx) => option.id = idx ) 25 | Vue.set(state, tasktype, taskOptions) 26 | } 27 | 28 | const module: Module = { 29 | namespaced: true, 30 | state: GetInitState(), 31 | mutations: { 32 | Init: ( state ) => { 33 | const initState = GetInitState() 34 | Vue.set(state, TASKTYPES, initState.taskTypes) 35 | Vue.set(state, TASKSOURCES, initState.taskSources) 36 | Vue.set(state, DAYWORKLIMITTIME, initState.dayWorkLimitTime) 37 | }, 38 | SetTaskTypes: ( state, taskTypes: Array ) => SetOptions(state, TASKTYPES, taskTypes), 39 | SetTaskSources: ( state, taskSources: Array ) => SetOptions(state, TASKSOURCES, taskSources), 40 | SetDayWorkLimitTime: ( state, dayWorkLimitTime: number ) => state.dayWorkLimitTime = dayWorkLimitTime, 41 | }, 42 | actions: { 43 | }, 44 | modules: { 45 | } 46 | } 47 | 48 | export default module -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/store/twoColumn.ts: -------------------------------------------------------------------------------- 1 | import { ITwoColumn } from '@/models/twoColumn' 2 | import { Module } from 'vuex/types/index' 3 | 4 | const GetInitState = (): ITwoColumn => ({ 5 | isShowSidebar: false, 6 | isActiveTwoColumn: false, 7 | }) 8 | 9 | const module: Module = { 10 | namespaced: true, 11 | state: GetInitState(), 12 | mutations: { 13 | Init: ( state ) => { 14 | const initState = GetInitState() 15 | Object.keys(initState).forEach( key => Reflect.set( state, key, Reflect.get(initState, key) ) ) 16 | }, 17 | ActiveSidebar: ( state ) => state.isShowSidebar = true, 18 | DisActiveSidebar: ( state ) => state.isShowSidebar = false, 19 | ToggleSidebar: ( state ) => state.isShowSidebar = !state.isShowSidebar, 20 | ActiveTwoColumn: ( state ) => state.isActiveTwoColumn = true, 21 | DisActiveTwoColumn: ( state ) => state.isActiveTwoColumn = false, 22 | }, 23 | actions: { 24 | }, 25 | modules: { 26 | } 27 | } 28 | 29 | export default module -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/store/webSocket.ts: -------------------------------------------------------------------------------- 1 | import { WSHandler } from '@/api/webSocket' 2 | import { IQueryTasks } from '@/models/tasks' 3 | import { IWebSocket } from '@/models/webSocket' 4 | import Vue from 'vue' 5 | import { Module } from 'vuex/types/index' 6 | 7 | const GetInitState = (): IWebSocket => ({ 8 | wsHandler: undefined, 9 | taskEditorQueryTasks: undefined, 10 | }) 11 | 12 | const module: Module = { 13 | namespaced: true, 14 | state: GetInitState(), 15 | mutations: { 16 | Init: ( state ) => { 17 | const initState = GetInitState() 18 | Vue.set(state, "wsHandler", initState.wsHandler) 19 | }, 20 | SetWSHandler: ( state, wsHandler: WSHandler ) => { 21 | state.wsHandler = wsHandler 22 | }, 23 | SetTaskEditorGroup: ( state, queryTasks: IQueryTasks ) => { 24 | Vue.set(state, "taskEditorQueryTasks", queryTasks) 25 | }, 26 | }, 27 | actions: { 28 | }, 29 | modules: { 30 | } 31 | } 32 | 33 | export default module -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/authValidation.ts: -------------------------------------------------------------------------------- 1 | import { ValidationResults } from '@/models/authentication' 2 | import { Store } from 'vuex/types/index' 3 | import { AccountStatus } from '../models/constants/authentication' 4 | import { IRouteConfigs } from '../models/routeConfigs' 5 | import { IStore } from '../models/store' 6 | 7 | function ValidatePathExists(pathName: string, store: Store, routeConfigs: IRouteConfigs){ 8 | const isFoundPath = pathName in routeConfigs 9 | if( !isFoundPath ){ 10 | return ValidationResults.invalidPath 11 | } 12 | 13 | return ValidationResults.ok 14 | } 15 | 16 | function ValidateAccountStatus(pathName: string, store: Store, routeConfigs: IRouteConfigs){ 17 | const isRequiredApprovedAccount = routeConfigs[pathName].auth.isRequiredApprovedAccount 18 | const isAccountApproved = store.state.authentication.claims.accountStatus == AccountStatus.Approved 19 | if( isRequiredApprovedAccount && !isAccountApproved ){ 20 | return ValidationResults.invalidAccountStatus 21 | } 22 | 23 | return ValidationResults.ok 24 | } 25 | 26 | function ValidateAuthentication(pathName: string, store: Store, routeConfigs: IRouteConfigs){ 27 | const isRequiredAuthentication = routeConfigs[pathName].auth.isRequiredAuthentication 28 | const isAuthenticated = store.state.authentication.isAuthenticated 29 | if( isRequiredAuthentication && !isAuthenticated ){ 30 | return ValidationResults.invalidAuthentication 31 | } 32 | 33 | return ValidationResults.ok 34 | } 35 | 36 | function ValidateRole(pathName: string, store: Store, routeConfigs: IRouteConfigs){ 37 | const requiredRoles = routeConfigs[pathName].auth.requiredRoles 38 | const userRoleIds = store.state.authentication.claims.userRoles.map( x => x.id ) 39 | 40 | if( requiredRoles.length == 0 ){ 41 | return ValidationResults.ok 42 | } 43 | 44 | if( requiredRoles.filter( allowRole => userRoleIds.includes(allowRole) ).length !== requiredRoles.length ){ 45 | return ValidationResults.invalidRole 46 | } 47 | 48 | return ValidationResults.ok 49 | } 50 | 51 | function ValidateAuth(pathName: string, store: Store, routeConfigs: IRouteConfigs){ 52 | const chaninedValidater: Array<(pathName: string, store: Store, routeConfigs: IRouteConfigs) => ValidationResults> = [ 53 | ValidatePathExists, 54 | ValidateAccountStatus, 55 | ValidateAuthentication, 56 | ValidateRole, 57 | ] 58 | 59 | for (const Validater of chaninedValidater) { 60 | const result = Validater(pathName, store, routeConfigs) 61 | if( result != ValidationResults.ok ){ 62 | return result 63 | } 64 | } 65 | return ValidationResults.ok 66 | } 67 | 68 | export { 69 | ValidatePathExists, 70 | ValidateAccountStatus, 71 | ValidateAuthentication, 72 | ValidateRole, 73 | ValidateAuth, 74 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/authentication.ts: -------------------------------------------------------------------------------- 1 | // function GetGuid() { 2 | // return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => 3 | // (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 4 | // ); 5 | // } 6 | 7 | import { IClaims } from "@/models/authentication"; 8 | 9 | function GetGuid() { 10 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 11 | const r = Math.random() * 16 | 0, 12 | v = c == 'x' ? r : (r & 0x3 | 0x8); 13 | return v.toString(16); 14 | }); 15 | } 16 | 17 | const GetGuids = ( users: Array) => users.map( selectedUser => selectedUser.guid ) 18 | 19 | const IsEqualUsers = ( left: Array, right: Array ) => { 20 | const a = new Set(left) 21 | const b = new Set(right) 22 | const intersection = new Set([ ...a].filter( x => b.has(x) )) 23 | return a.size == intersection.size && b.size == intersection.size 24 | } 25 | 26 | export { 27 | GetGuid, 28 | GetGuids, 29 | IsEqualUsers, 30 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/breakPoint.ts: -------------------------------------------------------------------------------- 1 | // import { computed } from '@vue/composition-api' 2 | // import { useWindowSize } from 'vue-use-web' 3 | 4 | enum ScreenSize{ 5 | xl, 6 | lg, 7 | md, 8 | sm, 9 | xs, 10 | } 11 | 12 | function GetSize(size: number) { 13 | 14 | if( 1904 <= window.innerWidth ){ 15 | return ScreenSize.xl 16 | }else if( 1264 <= window.innerWidth && window.innerWidth < 1904 ){ 17 | return ScreenSize.lg 18 | }else if( 960 <= window.innerWidth && window.innerWidth < 1264 ){ 19 | return ScreenSize.md 20 | }else if( 600 <= window.innerWidth && window.innerWidth < 960 ){ 21 | return ScreenSize.sm 22 | }else{ 23 | return ScreenSize.xs 24 | } 25 | } 26 | 27 | // const GetScreenSize = () => computed( () => { 28 | // const { width, height } = useWindowSize({ 29 | // throttleMs: 100 30 | // }); 31 | 32 | // return GetSize(width.value) 33 | // }) 34 | 35 | export { 36 | ScreenSize, 37 | GetSize, 38 | // GetScreenSize, 39 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/components/layout/ResponsiveElement.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 89 | 90 | 93 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/components/transition/RippleTransition.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/components/transition/RippleTransitionFlip.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 57 | 58 | 61 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/components/transition/SimpleTransition.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 44 | 45 | 47 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/notification.ts: -------------------------------------------------------------------------------- 1 | import { PluginOptions, ToastOptions } from "vue-toastification/dist/types/src/types" 2 | import ToastInterface from "vue-toastification/dist/types/src/ts/interface" 3 | import { TYPE, POSITION } from "vue-toastification/dist/types/src/ts/constants"; 4 | 5 | function ToastSuccess(message: string, toast?: ReturnType, option?: (ToastOptions & { 6 | type?: TYPE.SUCCESS | undefined; 7 | }) | undefined){ 8 | const mergedOption = { 9 | timeout: 7 * 1000, 10 | ...option 11 | } as PluginOptions 12 | return toast?.success(message, mergedOption) 13 | } 14 | 15 | function ToastWarning(message: string, toast?: ReturnType, option?: (ToastOptions & { 16 | type?: TYPE.WARNING | undefined; 17 | }) | undefined){ 18 | const mergedOption = { 19 | timeout: 7 * 1000, 20 | ...option 21 | } as PluginOptions 22 | return toast?.warning(message, mergedOption) 23 | } 24 | 25 | function ToastError(message: string, toast?: ReturnType, option?: (ToastOptions & { 26 | type?: TYPE.WARNING | undefined; 27 | }) | undefined){ 28 | const mergedOption = { 29 | timeout: 7 * 1000, 30 | ...option 31 | } as PluginOptions 32 | return toast?.error(message, mergedOption) 33 | } 34 | 35 | export { 36 | ToastSuccess, 37 | ToastWarning, 38 | ToastError, 39 | } 40 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/period.ts: -------------------------------------------------------------------------------- 1 | import moment from "moment" 2 | import { ValidateDate } from "./taskDate" 3 | 4 | function IsDisabledSubmit(name: string, startDate: string, endDate: string, isLoading: boolean) { 5 | if( name.length == 0 || startDate.length == 0 || endDate.length == 0) { 6 | return true 7 | } 8 | 9 | if( ValidateDate(startDate) !== true || ValidateDate(endDate) !== true ) { 10 | return true 11 | } 12 | 13 | if( moment(startDate) > moment(endDate) ){ 14 | return true 15 | } 16 | 17 | if( isLoading ){ 18 | return true 19 | } 20 | 21 | return false 22 | } 23 | 24 | export { 25 | IsDisabledSubmit 26 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/taskDate.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | const dateFormat = "YYYY-MM-DD" 4 | 5 | function GetYesterday() { 6 | return moment().subtract(1, 'days').format(dateFormat) 7 | } 8 | 9 | function GetToday(){ 10 | return moment().format(dateFormat); 11 | } 12 | 13 | function ValidateDate(value: string | undefined){ 14 | return moment(value, dateFormat, true).isValid() || `Please input ${dateFormat}` 15 | } 16 | 17 | function FormatDate(value: string, format = dateFormat) { 18 | return moment(value).isValid() ? moment(value).format(format) : "" 19 | } 20 | 21 | function GetDateRange(startDate: string | undefined, endDate: string | undefined): Array { 22 | if( ValidateDate(startDate) !== true || ValidateDate(endDate) !== true ){ 23 | return [] 24 | } 25 | 26 | const dates = [] 27 | const currentDate = moment(startDate).add(-1, 'days').startOf('day') 28 | const lastDate = moment(endDate).add(1, 'days').startOf('day') 29 | 30 | while(currentDate.add(1, 'days').diff(lastDate) < 0) { 31 | dates.push(currentDate.clone().format(dateFormat)) 32 | } 33 | 34 | return dates 35 | } 36 | 37 | export { 38 | dateFormat, 39 | GetYesterday, 40 | GetToday, 41 | ValidateDate, 42 | FormatDate, 43 | GetDateRange, 44 | } 45 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/util/taskParameters.ts: -------------------------------------------------------------------------------- 1 | import { ITaskOption } from "@/models/tasks" 2 | 3 | const GetOptionIdByOption = ( storeTasksOptions: Array, value: ITaskOption ) => storeTasksOptions.find( x => x.guid === value.guid )?.id 4 | const GetOptionIdByGuid = ( storeTasksOptions: Array, value: string ) => storeTasksOptions.find( x => x.guid === value )?.id 5 | const GetOptionById = ( storeTasksOptions: Array, value: number ) => storeTasksOptions.find( x => x.id === value ) 6 | const GetOptionGuidById = ( storeTasksOptions: Array, value: number ) => storeTasksOptions.find( x => x.id === value )?.guid 7 | 8 | export { 9 | GetOptionIdByOption, 10 | GetOptionIdByGuid, 11 | GetOptionById, 12 | GetOptionGuidById, 13 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/views/IndividualSettings.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/views/PermissionEditor.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 80 | 81 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/views/Registration.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/views/TaskSummaryReporter.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/views/layouts/Center.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/views/layouts/Container.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/views/layouts/Tab.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | 32 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/vue-echarts.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-echarts' { 2 | import ECharts from 'vue-echarts' 3 | export default ECharts 4 | } 5 | 6 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/vuetify-image-input.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vuetify-image-input' { 2 | import VImageInput from 'vuetify-image-input' 3 | export default VImageInput 4 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/src/vuetify.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | import 'vuetify/dist/vuetify.min.css' 4 | import colors from 'vuetify/es5/util/colors' 5 | import { Iconfont } from 'vuetify/types/services/icons' 6 | 7 | Vue.use(Vuetify) 8 | 9 | const opts = { 10 | theme: { 11 | themes:{ 12 | light:{ 13 | // primary: colors.teal.base, 14 | // secondary: colors.cyan.base, 15 | // accent: colors.red.base, 16 | // error: colors.purple.base, 17 | // warning: colors.blue.base, 18 | // info: colors.deepPurple.base, 19 | // success: colors.lightGreen.base 20 | 21 | primary: colors.teal.base, 22 | secondary: colors.cyan.base, 23 | accent: colors.blueGrey.base, 24 | error: colors.deepOrange.base, 25 | warning: colors.orange.base, 26 | info: colors.blue.base, 27 | success: colors.green.base 28 | }, 29 | } 30 | 31 | }, 32 | } 33 | 34 | export default new Vuetify(opts) -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env", 17 | "jest" 18 | ], 19 | "paths": { 20 | "@/*": [ 21 | "src/*" 22 | ] 23 | }, 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "dom.iterable", 28 | "scripthost" 29 | ] 30 | }, 31 | "include": [ 32 | "src/**/*.ts", 33 | "src/**/*.tsx", 34 | "src/**/*.vue", 35 | "tests/**/*.ts", 36 | "tests/**/*.tsx" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | const webpack = require("webpack"); 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | // options... 7 | // publicPath: process.env.NODE_ENV === "production"? "http://1.2.3.4:8080/assets" : "/", 8 | // publicPath: process.env.NODE_ENV === "production"? "/assets" : "/", 9 | publicPath: 10 | process.env.NODE_ENV === "production" 11 | ? "/" 12 | : "/", 13 | configureWebpack: { 14 | plugins: [ 15 | new webpack.ProvidePlugin({ 16 | $: "jquery", 17 | jQuery: "jquery", 18 | "windows.jQuery": "jquery" 19 | }) 20 | ], 21 | }, 22 | css: { 23 | loaderOptions: { 24 | sass: { 25 | prependData: `@import "~@/scss/global_var.scss";` 26 | } 27 | }, 28 | sourceMap: true 29 | }, 30 | transpileDependencies: [ 31 | 'vue-echarts', 32 | 'resize-detector' 33 | ] 34 | }; 35 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/ClientApp/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.s(c|a)ss$/, 6 | use: [ 7 | 'vue-style-loader', 8 | 'css-loader', 9 | { 10 | loader: 'sass-loader', 11 | // Requires sass-loader@^7.0.0 12 | options: { 13 | implementation: require('sass'), 14 | indentedSyntax: true // optional 15 | }, 16 | // Requires sass-loader@^8.0.0 17 | options: { 18 | implementation: require('sass'), 19 | sassOptions: { 20 | indentedSyntax: true // optional 21 | }, 22 | }, 23 | }, 24 | ], 25 | }, 26 | ], 27 | } 28 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Controllers/ImageController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | using TimeTracker.DAL; 8 | using TimeTracker.DAL.DBModels.Auth; 9 | using TimeTracker.DAL.DBModels.Task; 10 | using TimeTracker.DAL.Models; 11 | using TimeTracker.Helper.Auth; 12 | using TimeTracker.Helper.Extensions; 13 | using TimeTracker.Models.Period; 14 | 15 | namespace TimeTracker.Controllers 16 | { 17 | [Route("api/[controller]/[action]")] 18 | [ApiController] 19 | [Authorize] 20 | public class ImageController : ControllerBase 21 | { 22 | protected readonly ApplicationDbContext _context; 23 | 24 | public ImageController(ApplicationDbContext applicationDbContext) 25 | { 26 | this._context = applicationDbContext; 27 | } 28 | 29 | protected UserImage _GetUserImage() 30 | { 31 | int? id = User.GetUserId(); 32 | return id == null ? null : this._context.UserImage.SingleOrDefault(x => x.User.Id == id); 33 | } 34 | 35 | protected void _CreateUserImage(string image) 36 | { 37 | int? userId = User.GetUserId(); 38 | this._context.UserImage.Add(new UserImage((int)userId, image)); 39 | this._context.SaveChanges(); 40 | } 41 | 42 | protected void _UpdateUserImage(UserImage userImage, string image) 43 | { 44 | userImage.Image = image; 45 | userImage.SetUpdatedDate(); 46 | this._context.SaveChanges(); 47 | } 48 | 49 | [HttpPost] 50 | public IActionResult GetImage() 51 | { 52 | var result = this._GetUserImage(); 53 | 54 | return Ok( result == null ? "" : result.Image ); 55 | } 56 | 57 | [HttpPost] 58 | public IActionResult CreateOrUpdateImage(UserImage tmpUserImage) 59 | { 60 | string image = tmpUserImage.Image; 61 | var userImage = this._GetUserImage(); 62 | 63 | if (userImage == null) 64 | { 65 | this._CreateUserImage(image); 66 | } 67 | else 68 | { 69 | this._UpdateUserImage(userImage, image); 70 | } 71 | 72 | return Ok(); 73 | } 74 | 75 | [HttpPost] 76 | public IActionResult DeleteImage() 77 | { 78 | var userImage = this._GetUserImage(); 79 | if (userImage == null) 80 | { 81 | return Ok(); 82 | } 83 | 84 | this._context.UserImage.Remove(userImage); 85 | this._context.SaveChanges(); 86 | 87 | return Ok(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Controllers/PeriodController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | using TimeTracker.DAL; 8 | using TimeTracker.DAL.DBModels.Task; 9 | using TimeTracker.DAL.Models; 10 | using TimeTracker.Helper.Auth; 11 | using TimeTracker.Models.Period; 12 | 13 | namespace TimeTracker.Controllers 14 | { 15 | [Route("api/[controller]/[action]")] 16 | [ApiController] 17 | [Authorize] 18 | public class PeriodController : ControllerBase 19 | { 20 | protected readonly ApplicationDbContext _context; 21 | 22 | public PeriodController(ApplicationDbContext applicationDbContext) 23 | { 24 | this._context = applicationDbContext; 25 | } 26 | 27 | [HttpPost] 28 | [AuthorizeRole(UserRoles.User)] 29 | public IActionResult GetPeriods() 30 | { 31 | var result = this._context.Period.Select(x => x).AsNoTracking(); 32 | return Ok(result); 33 | } 34 | 35 | [HttpPost] 36 | [AuthorizeRole(UserRoles.Admin)] 37 | public IActionResult CreatePeriods(IEnumerable periods) 38 | { 39 | foreach (var period in periods) 40 | { 41 | period.Guid = Guid.NewGuid(); 42 | } 43 | 44 | this._context.Period.AddRange(periods); 45 | this._context.SaveChanges(); 46 | 47 | var result = periods.Select(x => x.Guid); 48 | return Ok(result); 49 | } 50 | 51 | [HttpPost] 52 | [AuthorizeRole(UserRoles.Admin)] 53 | public IActionResult UpdatePeriods(IEnumerable queryPeriods) 54 | { 55 | bool isUpdate = false; 56 | foreach (var queryPeriod in queryPeriods) 57 | { 58 | var period = this._context.Period.Where(x => x.Guid == queryPeriod.Guid).SingleOrDefault(); 59 | 60 | if (period != null) 61 | { 62 | period.StartDate = queryPeriod.StartDate; 63 | period.EndDate = queryPeriod.EndDate; 64 | period.Name= queryPeriod.Name; 65 | period.SetUpdatedDate(); 66 | isUpdate = true; 67 | } 68 | } 69 | 70 | if (isUpdate) 71 | { 72 | this._context.SaveChanges(); 73 | } 74 | return Ok(); 75 | } 76 | 77 | [HttpPost] 78 | [AuthorizeRole(UserRoles.Admin)] 79 | public IActionResult DeletePeriods(IEnumerable queryPeriods) 80 | { 81 | var targetGuids = queryPeriods.Select(x => x.Guid); 82 | var targetPeriods = this._context.Period.Where(x => targetGuids.Contains(x.Guid)); 83 | 84 | if (targetPeriods != null) 85 | { 86 | this._context.Period.RemoveRange(targetPeriods); 87 | this._context.SaveChanges(); 88 | } 89 | 90 | return Ok(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Controllers/TaskEditorController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | using TimeTracker.DAL; 7 | using TimeTracker.DAL.Models; 8 | using TimeTracker.Helper.Auth; 9 | using TimeTracker.Models; 10 | using TimeTracker.Models.Task; 11 | 12 | namespace TimeTracker.Controllers 13 | { 14 | [Route("api/[controller]/[action]")] 15 | [ApiController] 16 | [Authorize] 17 | [AuthorizeRole(UserRoles.User)] 18 | public class TaskEditorController : ControllerBase 19 | { 20 | protected readonly ApplicationDbContext _context; 21 | protected readonly TaskHandler _taskHandler; 22 | 23 | public TaskEditorController(ApplicationDbContext applicationDbContext, TaskHandler taskHandler) 24 | { 25 | this._context = applicationDbContext; 26 | this._taskHandler = taskHandler; 27 | } 28 | 29 | [HttpPost] 30 | public IActionResult GetAccounts() 31 | { 32 | var accounts = this._context.User.Where(x => x.AccountStatus == AccountStatus.Approved).AsNoTracking().ToList(); 33 | var returnData = new List(); 34 | foreach (var account in accounts) 35 | { 36 | returnData.Add(new ApprovedUser(account)); 37 | } 38 | return Ok(returnData); 39 | } 40 | 41 | [HttpPost] 42 | public IActionResult GetDaysData(QueryTasks queryTasks) 43 | { 44 | var result = this._taskHandler.GetDaysData(queryTasks); 45 | return Ok(result); 46 | } 47 | 48 | [HttpPost] 49 | public IActionResult CreateTask(CreateTask createTask) 50 | { 51 | var taskDays = this._taskHandler.CreateAndSelectTaskDay(createTask.Tasks, createTask.OwnerGuid); 52 | 53 | this._taskHandler.CreateTasks(createTask.Tasks, taskDays); 54 | 55 | var tasksGuids = createTask.Tasks.Select(x => x.Guid).ToList(); 56 | 57 | return Ok(tasksGuids); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Controllers/TaskReporterController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using System.Linq; 5 | using TimeTracker.DAL; 6 | using TimeTracker.DAL.Models; 7 | using TimeTracker.Helper.Auth; 8 | using TimeTracker.Models.Task; 9 | 10 | namespace TimeTracker.Controllers 11 | { 12 | [Route("api/[controller]/[action]")] 13 | [ApiController] 14 | [Authorize] 15 | [AuthorizeRole(UserRoles.User)] 16 | public class TaskReporterController : ControllerBase 17 | { 18 | protected readonly ApplicationDbContext _context; 19 | protected readonly TaskReportHandler _taskReportHandler; 20 | private readonly ILogger _logger; 21 | 22 | public TaskReporterController(ApplicationDbContext applicationDbContext, TaskReportHandler taskReportHandler, ILogger logger) 23 | { 24 | this._context = applicationDbContext; 25 | this._taskReportHandler = taskReportHandler; 26 | _logger = logger; 27 | } 28 | 29 | [HttpPost] 30 | public IActionResult GetSimpleSummary(QueryPeopleTasks queryPeopleTasks) 31 | { 32 | return Ok(this._taskReportHandler.GetSimpleSummary(queryPeopleTasks)); 33 | } 34 | 35 | [HttpPost] 36 | public IActionResult GetTaskTimeSummaryDetail(QueryPeopleTasks queryPeopleTasks) 37 | { 38 | return Ok(this._taskReportHandler.GetTaskTimeSummaryDetail(queryPeopleTasks)); 39 | } 40 | 41 | [HttpPost] 42 | public IActionResult GetTaskTypeSummary(QueryPeopleTasks queryPeopleTasks) 43 | { 44 | return Ok(this._taskReportHandler.GetTaskTypeSummary(queryPeopleTasks)); 45 | } 46 | 47 | [HttpPost] 48 | public IActionResult GetTaskSourceSummary(QueryPeopleTasks queryPeopleTasks) 49 | { 50 | return Ok(this._taskReportHandler.GetTaskSourceSummary(queryPeopleTasks)); 51 | } 52 | 53 | [HttpPost] 54 | public IActionResult GetTaskTimeSummary(QueryPeopleTasks queryPeopleTasks) 55 | { 56 | return Ok(this._taskReportHandler.GetTaskTimeSummary(queryPeopleTasks)); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/Attributes/SqlDefaultValueAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TimeTracker.DAL.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 6 | public class SqlDefaultValueAttribute : Attribute 7 | { 8 | public string DefaultValue { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/Attributes/UniqueAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TimeTracker.DAL.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 6 | public class UniqueAttribute : Attribute 7 | { 8 | public string DefaultValue { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Auth/MapUserRole.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | using TimeTracker.DAL.DBModels.Auth; 3 | using TimeTracker.DAL.DBModelsBase; 4 | 5 | namespace TimeTracker.DAL.DBModels.Auth 6 | { 7 | public class MapUserRole: EntityDateInfo 8 | { 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; set; } 11 | 12 | public int UserId { get; set; } 13 | public User User { get; set; } 14 | 15 | 16 | public int UserRolesId { get; set; } 17 | public UserRole UserRoles { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Auth/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text.Json.Serialization; 6 | using TimeTracker.DAL.Attributes; 7 | using TimeTracker.DAL.DBModels.Task; 8 | using TimeTracker.DAL.DBModelsBase; 9 | using TimeTracker.DAL.Models; 10 | using TimeTracker.Helper.Auth; 11 | 12 | namespace TimeTracker.DAL.DBModels.Auth 13 | { 14 | public class User: EntityDateInfo 15 | { 16 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 17 | public int Id { get; set; } 18 | 19 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 20 | [Unique] 21 | public Guid Guid { get; set; } 22 | 23 | //[Column(TypeName = "NVARCHAR")] 24 | [StringLength(256)] 25 | public string Name { get; set; } 26 | 27 | [Required] 28 | [DataType(DataType.EmailAddress)] 29 | //[Column(TypeName = "NVARCHAR")] 30 | [StringLength(256)] 31 | [Unique] 32 | public string Email { get; set; } 33 | [Required] 34 | public string PasswordHash { get; set; } 35 | [Required] 36 | public AccountStatus AccountStatus { get; set; } 37 | 38 | public ICollection UserRoles { get; set; } 39 | [JsonIgnore] 40 | public List MapUserRoles { get; set; } 41 | 42 | public ICollection TaskDay { get; set; } 43 | //public ICollection Task { get; set; } 44 | //public ICollection TaskTimeRange { get; set; } 45 | 46 | public User() 47 | { 48 | 49 | } 50 | 51 | public User(string email, string password) 52 | { 53 | this.Email = email; 54 | this.PasswordHash = SecurePasswordHasher.Hash(password); 55 | this.AccountStatus = AccountStatus.Uncheck; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Auth/UserImage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text.Json.Serialization; 6 | using TimeTracker.DAL.Attributes; 7 | using TimeTracker.DAL.DBModels.Task; 8 | using TimeTracker.DAL.DBModelsBase; 9 | using TimeTracker.DAL.Models; 10 | using TimeTracker.Helper.Auth; 11 | 12 | namespace TimeTracker.DAL.DBModels.Auth 13 | { 14 | public class UserImage : EntityDateInfo 15 | { 16 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 17 | public Guid Guid { get; set; } 18 | 19 | /// 20 | /// base 64 string 21 | /// 22 | public string Image { get; set; } 23 | 24 | [ForeignKey("User")] 25 | public int UserId { get; set; } 26 | 27 | public User User { get; set; } 28 | 29 | public UserImage(){} 30 | 31 | public UserImage(int userId, string image) 32 | { 33 | this.Guid = Guid.NewGuid(); 34 | 35 | this.Image = image; 36 | 37 | this.UserId = userId; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Auth/UserRole.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using System.Text.Json.Serialization; 5 | using TimeTracker.DAL.Attributes; 6 | using TimeTracker.DAL.DBModelsBase; 7 | 8 | namespace TimeTracker.DAL.DBModels.Auth 9 | { 10 | public class UserRole: EntityDateInfo 11 | { 12 | [Key] 13 | public int Id { get; set; } 14 | /// 15 | /// for matching program code 16 | /// 17 | [Required] 18 | //[Column(TypeName = "NVARCHAR")] 19 | [StringLength(256)] 20 | [Unique] 21 | public string CodeName { get; set; } 22 | //[Column(TypeName = "NVARCHAR")] 23 | [StringLength(256)] 24 | public string DisplayName { get; set; } 25 | [JsonIgnore] 26 | public ICollection Users { get; set; } 27 | [JsonIgnore] 28 | public List MapUserRoles { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/DayWorkLimitTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using TimeTracker.DAL.Attributes; 5 | using TimeTracker.DAL.DBModelsBase; 6 | 7 | namespace TimeTracker.DAL.DBModels.Task 8 | { 9 | /// 10 | /// 工作時間上限 11 | /// 12 | public class DayWorkLimitTime: EntityDateInfo 13 | { 14 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 15 | public Guid Guid { get; set; } 16 | 17 | /// 18 | /// 工作時間上限 19 | /// 20 | [Required] 21 | [Range(0.0, 24.0)] 22 | public double LimitWorkTime { get; set; } = 7.5; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/NonWorkDays.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using TimeTracker.DAL.Attributes; 5 | using TimeTracker.DAL.DBModelsBase; 6 | 7 | namespace TimeTracker.DAL.DBModels.Task 8 | { 9 | /// 10 | /// 非工作日 11 | /// 12 | public class NonWorkDays: EntityDateInfo 13 | { 14 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 15 | public Guid Guid { get; set; } 16 | 17 | /// 18 | /// 非工作日 年月日 year-month-day 19 | /// 20 | [DataType(DataType.Date)] 21 | [Column(TypeName = "Date")] 22 | [Required] 23 | public DateTime NonWorkDate { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/Period.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using TimeTracker.DAL.DBModelsBase; 5 | 6 | namespace TimeTracker.DAL.DBModels.Task 7 | { 8 | /// 9 | /// 週期 10 | /// 11 | public class Period: EntityDateInfo 12 | { 13 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 14 | public Guid Guid { get; set; } 15 | 16 | /// 17 | /// 年月日 year-month-day 18 | /// 19 | [DataType(DataType.Date)] 20 | [Column(TypeName = "Date")] 21 | [Required] 22 | public DateTime StartDate { get; set; } 23 | 24 | /// 25 | /// 年月日 year-month-day 26 | /// 27 | [DataType(DataType.Date)] 28 | [Column(TypeName = "Date")] 29 | [Required] 30 | public DateTime EndDate { get; set; } 31 | 32 | //[Column(TypeName = "NVARCHAR")] 33 | [StringLength(256)] 34 | public string Name { get; set; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/Task.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using TimeTracker.DAL.DBModelsBase; 4 | 5 | namespace TimeTracker.DAL.DBModels.Task 6 | { 7 | /// 8 | /// 一個Task的資訊 9 | /// 10 | public class Task: EntityTask 11 | { 12 | /// 13 | /// 花費時數 14 | /// 15 | [Required] 16 | [Range(0.0, 24.0)] 17 | public double ConsumeTime { get; set; } 18 | 19 | public TaskDay TaskDay { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/TaskDay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using TimeTracker.DAL.DBModels.Auth; 5 | using TimeTracker.DAL.DBModelsBase; 6 | 7 | namespace TimeTracker.DAL.DBModels.Task 8 | { 9 | public class TaskDay: EntityTaskBase 10 | { 11 | /// 12 | /// 是否請假 13 | /// 14 | [Required] 15 | public bool IsLeave { get; set; } 16 | 17 | public User User { get; set; } 18 | 19 | public ICollection Task { get; set; } 20 | 21 | public ICollection TaskTimeRange { get; set; } 22 | 23 | public TaskDay(){} 24 | 25 | public TaskDay(DateTime date, User user) 26 | { 27 | this.Guid = Guid.NewGuid(); 28 | this.Date = date; 29 | 30 | this.IsLeave = false; 31 | this.User = user; 32 | this.Task = null; 33 | this.TaskTimeRange = null; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/TaskSource.cs: -------------------------------------------------------------------------------- 1 | using TimeTracker.DAL.DBModelsBase; 2 | 3 | namespace TimeTracker.DAL.DBModels.Task 4 | { 5 | public class TaskSource: EntityOptions 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/TaskTimeRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using TimeTracker.DAL.DBModelsBase; 4 | 5 | namespace TimeTracker.DAL.DBModels.Task 6 | { 7 | public class TaskTimeRange: EntityTask 8 | { 9 | /// 10 | /// 起始時間 11 | /// 12 | [Required] 13 | public DateTime StartTime { get; set; } 14 | 15 | /// 16 | /// 結束時間 17 | /// 18 | [Required] 19 | public DateTime EndTime { get; set; } 20 | 21 | public TaskDay TaskDay { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/Task/TaskType.cs: -------------------------------------------------------------------------------- 1 | using TimeTracker.DAL.DBModelsBase; 2 | 3 | namespace TimeTracker.DAL.DBModels.Task 4 | { 5 | public class TaskType: EntityOptions 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModels/UserRole.cs: -------------------------------------------------------------------------------- 1 | namespace TimeTracker.DAL.DBModels 2 | { 3 | internal class UserRole 4 | { 5 | public int Id { get; set; } 6 | public string CodeName { get; set; } 7 | public string DisplayName { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModelsBase/EntityDateInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using System.Text.Json.Serialization; 5 | using TimeTracker.DAL.Attributes; 6 | 7 | namespace TimeTracker.DAL.DBModelsBase 8 | { 9 | public class EntityDateInfo 10 | { 11 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 12 | [DefaultValue(typeof(DateTime), "")] 13 | //[SqlDefaultValue(DefaultValue = "getutcdate()")] 14 | [JsonIgnore] 15 | public DateTime CreatedDate { get; set; } = DateTime.UtcNow; 16 | 17 | 18 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 19 | [DefaultValue(typeof(DateTime), "")] 20 | //[SqlDefaultValue(DefaultValue = "getutcdate()")] 21 | [JsonIgnore] 22 | public DateTime UpdatedDate { get; set; } = DateTime.UtcNow; 23 | 24 | public void SetUpdatedDate() 25 | { 26 | this.UpdatedDate = DateTime.UtcNow; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModelsBase/EntityOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using TimeTracker.DAL.Attributes; 5 | 6 | namespace TimeTracker.DAL.DBModelsBase 7 | { 8 | public class EntityOptions: EntityDateInfo 9 | { 10 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 11 | public Guid Guid { get; set; } 12 | 13 | /// 14 | /// for matching program code 15 | /// 16 | [Required] 17 | //[Column(TypeName = "NVARCHAR")] 18 | [StringLength(256)] 19 | [Unique] 20 | public string CodeName { get; set; } 21 | 22 | //[Column(TypeName = "NVARCHAR")] 23 | [StringLength(256)] 24 | public string DisplayName { get; set; } 25 | 26 | public bool IsActive { get; set; } = true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModelsBase/EntityTask.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using TimeTracker.DAL.DBModels.Task; 3 | 4 | namespace TimeTracker.DAL.DBModelsBase 5 | { 6 | public class EntityTask: EntityTaskBase 7 | { 8 | /// 9 | /// 顯示順序 10 | /// 11 | public int DisplayOrder { get; set; } 12 | 13 | /// 14 | /// 工作事項類型 15 | /// 16 | public TaskType TaskType { get; set; } 17 | 18 | /// 19 | /// 工作事項來源 20 | /// 21 | public TaskSource TaskSource { get; set; } 22 | 23 | /// 24 | /// 工作事項名稱 25 | /// 26 | /// [Column(TypeName = "NVARCHAR")] 27 | [StringLength(256)] 28 | public string TaskName { get; set; } 29 | 30 | /// 31 | /// 工作內容 32 | /// 33 | /// [Column(TypeName = "NVARCHAR")] 34 | [StringLength(256)] 35 | public string TaskContent { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/DBModelsBase/EntityTaskBase.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using TimeTracker.Helper.Attributes; 6 | 7 | namespace TimeTracker.DAL.DBModelsBase 8 | { 9 | public class EntityTaskBase: EntityDateInfo 10 | { 11 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 12 | public Guid Guid { get; set; } 13 | 14 | /// 15 | /// 年月日 year-month-day 16 | /// 17 | [DataType(DataType.Date)] 18 | [Column(TypeName = "Date")] 19 | [Required] 20 | public DateTime Date { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/IUserAuthHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | using TimeTracker.DAL.Models; 7 | 8 | namespace TimeTracker.DAL 9 | { 10 | public interface IUserAuthHandler 11 | where TEntity : class 12 | { 13 | CreateAccountResult Create(TEntity entity); 14 | 15 | void Update(TEntity entity); 16 | 17 | void Delete(TKey id); 18 | 19 | TEntity FindById(TKey id); 20 | 21 | IEnumerable Find(Expression> expression); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/Models/AccountStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.DAL.Models 7 | { 8 | public enum AccountStatus 9 | { 10 | Uncheck = 0, // 未審核用戶 11 | Approved = 1, // 合法用戶 12 | Rejected = 2, // 拒絕用戶 13 | Suspend = 3, // 停權用戶 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/Models/CreateAccountResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.DAL.Models 7 | { 8 | public enum CreateAccountResult 9 | { 10 | Ok = 0, 11 | AccountExist = 1, 12 | Fail = 2, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/Models/UserRoles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.DAL.Models 7 | { 8 | public enum UserRoles 9 | { 10 | Admin = 1, 11 | User = 2, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/DAL/UserAuthHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using TimeTracker.DAL.DBModels; 7 | using TimeTracker.DAL.DBModels.Auth; 8 | using TimeTracker.DAL.Models; 9 | 10 | namespace TimeTracker.DAL 11 | { 12 | public class UserAuthHandler : IUserAuthHandler 13 | { 14 | private readonly ApplicationDbContext _context; 15 | 16 | public UserAuthHandler(ApplicationDbContext context) 17 | { 18 | this._context = context; 19 | } 20 | 21 | public CreateAccountResult Create(User entity) 22 | { 23 | var check = _context.User.SingleOrDefault(x => x.Email == entity.Email); 24 | if (check != null) 25 | { 26 | return CreateAccountResult.AccountExist; 27 | } 28 | 29 | _context.User.Add(entity); 30 | _context.SaveChanges(); 31 | 32 | _context.MapUserRoles.Add(new MapUserRole() 33 | { 34 | UserId = entity.Id, 35 | UserRolesId = (int)Models.UserRoles.User, 36 | }); 37 | _context.SaveChanges(); 38 | 39 | return CreateAccountResult.Ok; 40 | } 41 | 42 | public void Delete(int id) 43 | { 44 | var currentUser = FindById(id); 45 | if (currentUser == null) 46 | { 47 | return; 48 | } 49 | _context.User.Remove(currentUser); 50 | _context.SaveChanges(); 51 | } 52 | 53 | public void Update(User entity) 54 | { 55 | var currentUser = FindById(entity.Id); 56 | if (currentUser == null) 57 | { 58 | return; 59 | } 60 | entity.SetUpdatedDate(); 61 | _context.Entry(currentUser).CurrentValues.SetValues(entity); 62 | _context.SaveChanges(); 63 | } 64 | 65 | public IEnumerable Find(Expression> expression) 66 | { 67 | return _context.User.Where(expression); 68 | } 69 | 70 | public User FindById(int id) 71 | { 72 | return _context.User 73 | .Include(x => x.UserRoles) 74 | .SingleOrDefault(x => x.Id == id); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Attributes/DateFormatConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text.Json; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace TimeTracker.Helper.Attributes 7 | { 8 | public class DateFormatConverter : JsonConverter 9 | { 10 | public override DateTime Read( 11 | ref Utf8JsonReader reader, 12 | Type typeToConvert, 13 | JsonSerializerOptions options) => 14 | DateTime.Parse(reader.GetString(), CultureInfo.InvariantCulture); 15 | 16 | public override void Write( 17 | Utf8JsonWriter writer, 18 | DateTime dateTimeValue, 19 | JsonSerializerOptions options) => 20 | writer.WriteStringValue(dateTimeValue.ToString( 21 | "yyyy-MM-dd", CultureInfo.InvariantCulture)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Attributes/GuidNotEmptyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace TimeTracker.Helper.Attributes 5 | { 6 | [AttributeUsage( 7 | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, 8 | AllowMultiple = false)] 9 | public class GuidNotEmptyAttribute : ValidationAttribute 10 | { 11 | public const string DefaultErrorMessage = "The {0} field must not be empty"; 12 | public GuidNotEmptyAttribute() : base(DefaultErrorMessage) { } 13 | 14 | public override bool IsValid(object value) 15 | { 16 | //NotEmpty doesn't necessarily mean required 17 | if (value is null) 18 | { 19 | return true; 20 | } 21 | 22 | switch (value) 23 | { 24 | case Guid guid: 25 | return guid != Guid.Empty; 26 | default: 27 | return true; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Attributes/ListNotEmptyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace TimeTracker.Helper.Attributes 9 | { 10 | public class ListNotEmptyAttribute : ValidationAttribute 11 | { 12 | public override bool IsValid(object value) 13 | { 14 | var list = value as IList; 15 | if (list != null) 16 | { 17 | return list.Count > 0; 18 | } 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Auth/AuthorizeRoleAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using TimeTracker.DAL; 6 | using TimeTracker.DAL.DBModels.Auth; 7 | using TimeTracker.DAL.Models; 8 | using TimeTracker.Helper.Extensions; 9 | 10 | namespace TimeTracker.Helper.Auth 11 | { 12 | public class AuthorizeRoleAttribute : TypeFilterAttribute 13 | { 14 | public AuthorizeRoleAttribute(UserRoles userRole) 15 | : base(typeof(AuthorizeActionFilter)) 16 | { 17 | Arguments = new object[] { userRole }; 18 | } 19 | } 20 | 21 | public class AuthorizeActionFilter : IAuthorizationFilter 22 | { 23 | private readonly UserRoles _userRole; 24 | protected readonly IUserAuthHandler _authHandler; 25 | public AuthorizeActionFilter(UserRoles userRole, IUserAuthHandler authHandler) 26 | { 27 | this._userRole = userRole; 28 | this._authHandler = authHandler; 29 | } 30 | 31 | public void OnAuthorization(AuthorizationFilterContext context) 32 | { 33 | int? id = context.HttpContext.User.GetUserId(); 34 | if (id == null) 35 | { 36 | context.Result = new UnauthorizedResult(); 37 | return; 38 | } 39 | 40 | var user = this._authHandler.FindById((int)id); 41 | if (user == null) 42 | { 43 | context.Result = new UnauthorizedResult(); 44 | return; 45 | } 46 | 47 | bool isAccountValid = user.AccountStatus == AccountStatus.Approved; 48 | if (!isAccountValid) 49 | { 50 | context.Result = new UnauthorizedResult(); 51 | return; 52 | } 53 | 54 | IEnumerable roles = user.UserRoles; 55 | bool isAuthorized = roles.Any(x => x.CodeName.ToLower() == this._userRole.ToString().ToLower() ); 56 | if (!isAuthorized) 57 | { 58 | context.Result = new ForbidResult(); 59 | return; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Extensions/DBContextExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using TimeTracker.DAL; 6 | 7 | namespace TimeTracker.Helper.Extensions 8 | { 9 | public static class DBContextExtension 10 | { 11 | // Ref: https://stackoverflow.com/questions/42993860/entity-framework-core-update-many-to-many 12 | public static void TryUpdateManyToMany(this ApplicationDbContext db, IEnumerable currentItems, IEnumerable newItems, Func getKey) where T : class 13 | { 14 | db.Set().RemoveRange(currentItems.Except(newItems, getKey)); 15 | db.Set().AddRange(newItems.Except(currentItems, getKey)); 16 | } 17 | 18 | public static IEnumerable Except(this IEnumerable items, IEnumerable other, Func getKeyFunc) 19 | { 20 | return items 21 | .GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems }) 22 | .SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp }) 23 | .Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T))) 24 | .Select(t => t.t.item); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Extensions/DateTimeExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TimeTracker.Helper.Extensions 5 | { 6 | public static class DateTimeExtension 7 | { 8 | public static IEnumerable EachDay(this DateTime start, DateTime end) 9 | { 10 | for (var day = start.Date; day.Date <= end.Date; day = day.AddDays(1)) 11 | yield return day; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Extensions/SessionAuthExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Security.Claims; 7 | using System.Threading.Tasks; 8 | 9 | namespace TimeTracker.Helper.Extensions 10 | { 11 | public static class SessionAuthExtensions 12 | { 13 | /// 14 | /// return null if not log in 15 | /// 16 | /// 17 | /// 18 | public static int? GetUserId(this ClaimsPrincipal claimsPrincipal) 19 | { 20 | string idString = claimsPrincipal.FindFirstValue(ClaimTypes.Sid); 21 | int id = -1; 22 | 23 | if (!Int32.TryParse(idString, out id)) 24 | { 25 | return null; 26 | } 27 | 28 | return id; 29 | } 30 | 31 | /// 32 | /// return null if not log in 33 | /// 34 | /// 35 | /// 36 | public static Guid? GetUserGuid(this ClaimsPrincipal claimsPrincipal) 37 | { 38 | string idString = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier); 39 | Guid guid; 40 | 41 | if (Guid.TryParse(idString, out guid)) 42 | { 43 | return null; 44 | } 45 | 46 | return guid; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Helper/Extensions/SessionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace TimeTracker.Helper.Extensions 9 | { 10 | public static class SessionExtensions 11 | { 12 | public static void SetObjectAsJson(this ISession session, string key, object value) 13 | { 14 | session.SetString(key, JsonConvert.SerializeObject(value)); 15 | } 16 | 17 | public static T GetObjectFromJson(this ISession session, string key) 18 | { 19 | var value = session.GetString(key); 20 | return value == null ? default(T) : JsonConvert.DeserializeObject(value); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Hubs/BaseWSHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using TimeTracker.DAL; 7 | using TimeTracker.Helper.Extensions; 8 | using TimeTracker.Hubs.Models; 9 | using TimeTracker.Models.Task; 10 | 11 | namespace TimeTracker.Hubs 12 | { 13 | public class BaseWSHub : Hub 14 | { 15 | protected readonly ApplicationDbContext _context; 16 | protected readonly TaskHandler _taskHandler; 17 | protected readonly WSHubHandler _wsHubHandler; 18 | 19 | public BaseWSHub(ApplicationDbContext context, TaskHandler taskHandler, WSHubHandler wsHubHandler) 20 | { 21 | this._context = context; 22 | this._taskHandler = taskHandler; 23 | this._wsHubHandler = wsHubHandler; 24 | } 25 | 26 | #region Maintaine Group 27 | protected async Task RangeAddToTaskEditorGroup(QueryTasks queryTasks) 28 | { 29 | foreach (DateTime day in queryTasks.StartDate.EachDay(queryTasks.EndDate)) 30 | { 31 | await AddToTaskEditorGroup(queryTasks.OwnerGuid, day); 32 | } 33 | } 34 | 35 | protected async Task AddToTaskEditorGroup(Guid guid, DateTime date) 36 | { 37 | await AddToGroup(_wsHubHandler.GetTaskEditorGroupName(guid, date)); 38 | } 39 | 40 | protected async Task RangeRemoveFromTaskEditorGroup(QueryTasks queryTasks) 41 | { 42 | foreach (DateTime day in queryTasks.StartDate.EachDay(queryTasks.EndDate)) 43 | { 44 | await RemoveFromTaskEditorGroup(queryTasks.OwnerGuid, day); 45 | } 46 | } 47 | 48 | protected async Task RemoveFromTaskEditorGroup(Guid guid, DateTime date) 49 | { 50 | await RemoveFromGroup(_wsHubHandler.GetTaskEditorGroupName(guid, date)); 51 | } 52 | #endregion 53 | 54 | #region Basic Methods 55 | protected async Task AddToGroup(string groupName) 56 | { 57 | await Groups.AddToGroupAsync(Context.ConnectionId, groupName); 58 | } 59 | protected async Task RemoveFromGroup(string groupName) 60 | { 61 | await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); 62 | } 63 | 64 | protected async Task GroupBroadcast(string groupName, string eventName, object payload) 65 | { 66 | await Clients 67 | //.Groups(groupName, Context.ConnectionId) 68 | .GroupExcept(groupName, Context.ConnectionId) 69 | .SendAsync(eventName, payload); 70 | } 71 | #endregion 72 | } 73 | } -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Hubs/Models/WSMapCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.Hubs.Models 7 | { 8 | public enum WSMapCode 9 | { 10 | GetUserInfo = 0, 11 | TaskEditorCreateTask = 1, 12 | TaskEditorDeleteTasks = 2, 13 | TaskEditorUpdateTaskRowOrder = 3, 14 | TaskEditorUpdateTaskCol = 4, 15 | TaskEditorUpdateIsLeave = 5, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Hubs/UserIdProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Security.Claims; 6 | using System.Threading.Tasks; 7 | 8 | namespace TimeTracker.Hubs 9 | { 10 | public class UserIdProvider: IUserIdProvider 11 | { 12 | public virtual string GetUserId(HubConnectionContext connection) 13 | { 14 | return connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Hubs/WSHubHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using System; 3 | using System.Threading.Tasks; 4 | using TimeTracker.Hubs.Models; 5 | using TimeTracker.DAL; 6 | 7 | namespace TimeTracker.Hubs 8 | { 9 | public class WSHubHandler where THub : Hub 10 | { 11 | protected readonly ApplicationDbContext _context; 12 | protected readonly IHubContext _hubContext; 13 | 14 | public WSHubHandler(ApplicationDbContext context, IHubContext hubContext) 15 | { 16 | this._context = context; 17 | this._hubContext = hubContext; 18 | } 19 | 20 | /// 21 | /// ask specific user to get user info by guid 22 | /// 23 | /// 24 | /// 25 | public Task ClientGetUserInfo(Guid guid) 26 | { 27 | return this._hubContext.Clients.User(guid.ToString()).SendAsync(WSMapCode.GetUserInfo.ToString(), guid.ToString()); 28 | } 29 | 30 | public string GetTaskEditorGroupName(Guid ownerGuid, DateTime date) 31 | { 32 | return GetTaskEditorGroupName(ownerGuid, date.ToString("yyyy-MM-dd")); 33 | } 34 | 35 | public string GetTaskEditorGroupName(Guid ownerGuid, string date) 36 | { 37 | return $"TaskEditor+{ownerGuid.ToString()}+{date}"; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using TimeTracker.DAL.DBModels.Auth; 3 | using TimeTracker.Models; 4 | 5 | namespace TimeTracker 6 | { 7 | public class MappingProfile : Profile 8 | { 9 | public MappingProfile() 10 | { 11 | // Add as many of these lines as you need to map your objects 12 | CreateMap(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/ApprovedUser.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using System; 3 | using TimeTracker.DAL.DBModels.Auth; 4 | 5 | namespace TimeTracker.Models 6 | { 7 | public class ApprovedUser 8 | { 9 | public Guid Guid { get; set; } 10 | 11 | public string Name { get; set; } 12 | 13 | public string Email { get; set; } 14 | 15 | public ApprovedUser(User user) 16 | { 17 | var mapperConfig = new MapperConfiguration(cfg => 18 | { 19 | cfg.CreateMap(); 20 | }); 21 | IMapper mapper = mapperConfig.CreateMapper(); 22 | mapper.Map(user, this); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/AuthenticationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace TimeTracker.Models 8 | { 9 | public class AuthenticationUser 10 | { 11 | [Required(AllowEmptyStrings = false)] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Required(AllowEmptyStrings = false)] 16 | public string Password { get; set; } 17 | 18 | public string Name { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Period/QueryPeriod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | using TimeTracker.Helper.Attributes; 8 | 9 | namespace TimeTracker.Models.Period 10 | { 11 | public class QueryPeriod 12 | { 13 | [GuidNotEmpty] 14 | public Guid Guid { get; set; } 15 | 16 | [JsonConverter(typeof(DateFormatConverter))] 17 | public DateTime StartDate { get; set; } 18 | 19 | [JsonConverter(typeof(DateFormatConverter))] 20 | public DateTime EndDate { get; set; } 21 | 22 | [StringLength(256)] 23 | public string Name { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/RegistUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace TimeTracker.Models 8 | { 9 | public class RegistUser 10 | { 11 | [Required(AllowEmptyStrings = false)] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Required(AllowEmptyStrings = false)] 16 | public string Password { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/SessionUserData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.Models 7 | { 8 | public class SessionUserData 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/CreateTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TimeTracker.Models.Task 5 | { 6 | public class CreateTask 7 | { 8 | public List Tasks { get; set; } 9 | 10 | public Guid OwnerGuid { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/DeleteTasks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.Models.Task 7 | { 8 | public class DeleteTasks 9 | { 10 | public Guid OwnerGuid { get; set; } 11 | 12 | /// 13 | /// yyy-MM-dd 14 | /// 15 | public string Date { get; set; } 16 | 17 | public List TaskGuids { get; set; } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/GetDaysDataResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json.Serialization; 4 | using TimeTracker.Helper.Attributes; 5 | 6 | namespace TimeTracker.Models.Task 7 | { 8 | public class GetDaysDataResponse 9 | { 10 | public Guid Guid { get; set; } 11 | 12 | [JsonConverter(typeof(DateFormatConverter))] 13 | public DateTime Date { get; set; } 14 | 15 | public bool IsLeave { get; set; } 16 | 17 | public List FormData { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/PersonSummary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using TimeTracker.Helper.Attributes; 7 | 8 | namespace TimeTracker.Models.Task 9 | { 10 | public class PersonSummary 11 | { 12 | [JsonConverter(typeof(DateFormatConverter))] 13 | public DateTime Date { get; set; } 14 | 15 | public double ConsumeTime 16 | { 17 | get{ 18 | return this.IsLeave ? this._LimitWorkTime : this._ConsumeTime; 19 | } 20 | } 21 | 22 | public double Overtime 23 | { 24 | get 25 | { 26 | return this.IsLeave ? 0 : 27 | this.ConsumeTime - this._LimitWorkTime > 0 ? this.ConsumeTime - this._LimitWorkTime : 0; 28 | } 29 | } 30 | 31 | public bool IsLeave { get; set; } = false; 32 | 33 | protected double _ConsumeTime { get; set; } 34 | 35 | protected double _LimitWorkTime { get; set; } 36 | 37 | public PersonSummary(){} 38 | 39 | public PersonSummary(DateTime date, double consumeTime, double limitWorkTime, bool isLeave) 40 | { 41 | this.Date = date; 42 | this._ConsumeTime = consumeTime; 43 | this._LimitWorkTime = limitWorkTime; 44 | this.IsLeave = isLeave; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/PieRow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.Models.Task 7 | { 8 | public class PieRow 9 | { 10 | public string Name { get; set; } 11 | public double Value { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/QueryPeopleTasks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | using TimeTracker.Helper.Attributes; 8 | 9 | namespace TimeTracker.Models.Task 10 | { 11 | public class QueryPeopleTasks 12 | { 13 | [ListNotEmpty(ErrorMessage ="At least a person is required")] 14 | public List OwnerGuids { get; set; } 15 | 16 | [Required] 17 | [JsonConverter(typeof(DateFormatConverter))] 18 | public DateTime StartDate { get; set; } 19 | 20 | [Required] 21 | [JsonConverter(typeof(DateFormatConverter))] 22 | public DateTime EndDate { get; set; } 23 | 24 | public QueryPeopleTasks(){} 25 | 26 | public QueryPeopleTasks(QueryTasks queryTasks) 27 | { 28 | this.OwnerGuids = (new[] { queryTasks.OwnerGuid }).ToList(); 29 | this.StartDate = queryTasks.StartDate; 30 | this.EndDate = queryTasks.EndDate; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/QueryTasks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | using TimeTracker.Helper.Attributes; 8 | 9 | namespace TimeTracker.Models.Task 10 | { 11 | public class QueryTasks 12 | { 13 | [GuidNotEmpty] 14 | public Guid OwnerGuid { get; set; } 15 | 16 | [Required] 17 | [JsonConverter(typeof(DateFormatConverter))] 18 | public DateTime StartDate { get; set; } 19 | 20 | [Required] 21 | [JsonConverter(typeof(DateFormatConverter))] 22 | public DateTime EndDate { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/SimpleSummary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using TimeTracker.Helper.Attributes; 7 | 8 | namespace TimeTracker.Models.Task 9 | { 10 | public class SimpleSummary 11 | { 12 | [JsonConverter(typeof(DateFormatConverter))] 13 | public DateTime Date { get; set; } 14 | 15 | public double ConsumeTime { get; set; } 16 | 17 | public double Overtime { get; set; } 18 | 19 | public bool IsLeave { get; set; } = false; 20 | 21 | public SimpleSummary(){} 22 | 23 | public SimpleSummary(MultiSimpleSummary multiSimpleSummary) 24 | { 25 | this.Date = multiSimpleSummary.Date; 26 | this.ConsumeTime = multiSimpleSummary.TotalConsumeTime; 27 | this.Overtime = multiSimpleSummary.TotalOvertime; 28 | } 29 | 30 | public SimpleSummary(PersonSummary personSummary) 31 | { 32 | this.Date = personSummary.Date; 33 | this.ConsumeTime = personSummary.ConsumeTime; 34 | this.Overtime = personSummary.Overtime; 35 | this.IsLeave = personSummary.IsLeave; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/UpdateIsLeave.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using TimeTracker.Helper.Attributes; 7 | 8 | namespace TimeTracker.Models.Task 9 | { 10 | public class UpdateIsLeave 11 | { 12 | public Guid OwnerGuid { get; set; } 13 | 14 | [JsonConverter(typeof(DateFormatConverter))] 15 | public DateTime Date { get; set; } 16 | 17 | public bool IsLeave { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/Task/UpdateTaskRowOrder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using TimeTracker.Helper.Attributes; 7 | 8 | namespace TimeTracker.Models.Task 9 | { 10 | public class UpdateTaskRowOrder 11 | { 12 | public Guid OwnerGuid { get; set; } 13 | 14 | /// 15 | /// Task guid 16 | /// 17 | public Guid Guid { get; set; } 18 | 19 | [JsonConverter(typeof(DateFormatConverter))] 20 | public DateTime Date { get; set; } 21 | 22 | public int DisplayOrder { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/UpdateAccount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using TimeTracker.DAL.DBModels; 7 | using TimeTracker.DAL.Models; 8 | 9 | namespace TimeTracker.Models 10 | { 11 | public class UpdateAccount 12 | { 13 | [Required] 14 | public Guid Guid { get; set; } 15 | 16 | public string Name { get; set; } 17 | public bool IsUpdateName { get; set; } 18 | 19 | public AccountStatus AccountStatus { get; set; } 20 | public bool IsUpdateAccountStatus { get; set; } 21 | 22 | public ICollection UserRoles { get; set; } 23 | public bool IsUpdateUserRoles { get; set; } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/UpdatePassword.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TimeTracker.Models 7 | { 8 | public class UpdatePassword 9 | { 10 | public string CurrentPassword { get; set; } 11 | public string Password { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/UpdateUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace TimeTracker.Models 8 | { 9 | public class UpdateUser 10 | { 11 | public string Name { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Models/UserInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using TimeTracker.DAL.DBModels.Auth; 4 | using TimeTracker.DAL.Models; 5 | 6 | namespace TimeTracker.Models 7 | { 8 | public class UserInfo 9 | { 10 | public Guid Guid { get; set; } 11 | public string Name { get; set; } 12 | public string Email { get; set; } 13 | 14 | public AccountStatus AccountStatus { get; set; } 15 | 16 | public ICollection UserRoles { get; set; } 17 | 18 | public UserInfo(User user) 19 | { 20 | this.Guid = user.Guid; 21 | this.Name = user.Name; 22 | this.Email = user.Email; 23 | this.AccountStatus = user.AccountStatus; 24 | this.UserRoles = user.UserRoles; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace TimeTracker.Pages 11 | { 12 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 13 | public class ErrorModel : PageModel 14 | { 15 | private readonly ILogger logger; 16 | 17 | public ErrorModel(ILogger _logger) 18 | { 19 | logger = _logger; 20 | } 21 | public string RequestId { get; set; } 22 | 23 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 24 | 25 | public void OnGet() 26 | { 27 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using TimeTracker 2 | @namespace TimeTracker.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace TimeTracker 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:62213", 7 | "sslPort": 44328 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "TimeTracker": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Debug", 6 | "Microsoft": "Debug", 7 | "Microsoft.Hosting.Lifetime": "Debug" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TimeTracker/TimeTracker/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "IsLaunchSPAServer": false, 11 | "DBType": "MS", // MS, PG 12 | "ConnectionStrings": { 13 | "MS": "Server=DESKTOP-3IQ61RH\\SQLEXPRESS;Database=TestTimeTracker;Integrated Security=True;", 14 | "PG": "User ID=postgres;Password=myPWD;Host=localhost;Port=5432;Database=TestTimeTracker;Pooling=true;Integrated Security=true;" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /release_windows/CreateDB_CreateTable_CreateInitData.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/release_windows/CreateDB_CreateTable_CreateInitData.sql -------------------------------------------------------------------------------- /release_windows/publish.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/release_windows/publish.zip -------------------------------------------------------------------------------- /screen_shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jchou24/TimeTracker/a87fb634c92e4607e44dc1528c0d3f0f3f6b362b/screen_shot.png --------------------------------------------------------------------------------