Update with new components
This commit is contained in:
314
support-portal-frontend/package-lock.json
generated
314
support-portal-frontend/package-lock.json
generated
@ -18,10 +18,8 @@
|
||||
"@angular/router": "~12.2.0",
|
||||
"@auth0/angular-jwt": "^3.0.1",
|
||||
"@josipv/angular-editor-k2": "^2.20.0",
|
||||
"@nicky-lenaers/ngx-scroll-to": "^9.0.0",
|
||||
"angular-notifier": "^9.1.0",
|
||||
"angular-notifier": "^10.0.0",
|
||||
"ng-particles": "^2.1.11",
|
||||
"ngx-owl-carousel-o": "^5.0.0",
|
||||
"ngx-typed-js": "^2.0.2",
|
||||
"rxjs": "~6.6.0",
|
||||
"subsink": "^1.0.2",
|
||||
@ -330,6 +328,30 @@
|
||||
"yarn": ">= 1.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core/node_modules/ajv": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz",
|
||||
"integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics": {
|
||||
"version": "12.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.18.tgz",
|
||||
@ -2643,20 +2665,6 @@
|
||||
"webpack": "^5.30.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nicky-lenaers/ngx-scroll-to": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nicky-lenaers/ngx-scroll-to/-/ngx-scroll-to-9.0.0.tgz",
|
||||
"integrity": "sha512-eS0vyx8qX4UTMluRYc+sQF/vJHCnAKiufWrwQRme0VURwp+RdOoZDZpYrOPTxPfx6CVj72arTeV9auDYa0WKtA==",
|
||||
"engines": {
|
||||
"node": ">=8.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^8.0.0 || ^9.0.0",
|
||||
"@angular/core": "^8.0.0 || ^9.0.0",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -3215,14 +3223,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz",
|
||||
"integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==",
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
@ -3256,6 +3265,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
@ -3266,22 +3299,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/angular-notifier": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/angular-notifier/-/angular-notifier-9.1.0.tgz",
|
||||
"integrity": "sha512-K8D8UljdC4P4TfjYB0v39Zs3WjgvPR7Vvp3yzGhcW4I8gXIqkz4xQSbqJbIZABCGEWdKqPyAN48wl7yg8Q3Urg==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/angular-notifier/-/angular-notifier-10.0.0.tgz",
|
||||
"integrity": "sha512-hVzFd41ZCT0O6EBlwN1cygl3qjXU4C41DVcaDrHK3CK5Y0JFbpFrJVuIAczKPjaul13rzJ/L7qzwobQHaTUwLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "2.0.x"
|
||||
"tslib": "2.3.x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">= 11.0.0 < 12.0.0",
|
||||
"@angular/core": ">= 11.0.0 < 12.0.0",
|
||||
"rxjs": ">= 6.0.0 < 7.0.0"
|
||||
"@angular/common": ">= 12.0.0 < 13.0.0",
|
||||
"@angular/core": ">= 12.0.0 < 13.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/angular-notifier/node_modules/tslib": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.1",
|
||||
@ -4637,28 +4671,6 @@
|
||||
"webpack": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-webpack-plugin/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/copy-webpack-plugin/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
@ -5104,28 +5116,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/css-minimizer-webpack-plugin/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/css-minimizer-webpack-plugin/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
@ -6653,6 +6643,23 @@
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||
@ -8472,10 +8479,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
@ -9402,28 +9410,6 @@
|
||||
"webpack": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-css-extract-plugin/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mini-css-extract-plugin/node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
@ -9763,19 +9749,6 @@
|
||||
"tsparticles": "^1.43.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-owl-carousel-o": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-owl-carousel-o/-/ngx-owl-carousel-o-5.1.1.tgz",
|
||||
"integrity": "sha512-AmaU02UzONGrBSj4K28ZWEZxLYySyiC7Vefq4VacjAhYxUR+HA9jdQghG4zM+QlVPkmgFdZeepeqZ3/h9fU3ug==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": " ^11.0.0-rc.0 || ^11.0.0",
|
||||
"@angular/core": "^11.0.0-rc.0 || ^11.0.0",
|
||||
"rxjs": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-typed-js": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-typed-js/-/ngx-typed-js-2.1.1.tgz",
|
||||
@ -13120,6 +13093,7 @@
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -13470,28 +13444,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
@ -14625,28 +14577,6 @@
|
||||
"webpack": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
@ -15303,28 +15233,6 @@
|
||||
"webpack": "^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-middleware/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webpack-dev-middleware/node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
@ -15398,22 +15306,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
@ -15613,12 +15505,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
@ -15875,28 +15761,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webpack/node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
|
||||
@ -20,10 +20,8 @@
|
||||
"@angular/router": "~12.2.0",
|
||||
"@auth0/angular-jwt": "^3.0.1",
|
||||
"@josipv/angular-editor-k2": "^2.20.0",
|
||||
"@nicky-lenaers/ngx-scroll-to": "^9.0.0",
|
||||
"angular-notifier": "^9.1.0",
|
||||
"angular-notifier": "^10.0.0",
|
||||
"ng-particles": "^2.1.11",
|
||||
"ngx-owl-carousel-o": "^5.0.0",
|
||||
"ngx-typed-js": "^2.0.2",
|
||||
"rxjs": "~6.6.0",
|
||||
"subsink": "^1.0.2",
|
||||
|
||||
@ -17,6 +17,8 @@ import { HomeComponent } from '../component/home/home.component';
|
||||
import { EventComponent } from '../component/event/event.component';
|
||||
import { BlogComponent } from '../component/blog/blog.component';
|
||||
import { EventFormComponent } from '../component/event-form/event-form.component';
|
||||
import { CareerComponent } from '../component/career/career.component';
|
||||
import { EducationComponent } from '../component/education/education.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: HomeComponent },
|
||||
@ -30,6 +32,8 @@ const routes: Routes = [
|
||||
{ path: 'eventForm', component: EventFormComponent, canActivate: [AuthenticationGuard] },
|
||||
{ path: 'eventForm/:id', component: EventFormComponent, canActivate: [AuthenticationGuard] },
|
||||
{ path: 'blogs', component: BlogComponent, canActivate: [AuthenticationGuard] },
|
||||
{ path: 'career', component: CareerComponent, canActivate: [AuthenticationGuard] },
|
||||
{ path: 'education', component: EducationComponent, canActivate: [AuthenticationGuard] },
|
||||
{ path: 'userManagement', component: UserComponent, canActivate: [AuthenticationGuard] },
|
||||
{ path: 'professorManagement', component: ProfessorComponent, canActivate: [AuthenticationGuard] },
|
||||
{
|
||||
|
||||
@ -31,6 +31,9 @@ import { NotificationModule } from '../notification/notification.module';
|
||||
import { EventFormComponent } from '../component/event-form/event-form.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AdminRoutingModule } from './admin-routing.module';
|
||||
import { CareerComponent } from '../component/career/career.component';
|
||||
import { EducationComponent } from '../component/education/education.component';
|
||||
import { CareerService } from '../service/career.service';
|
||||
// import { PagesModule } from '../pages/pages.module';
|
||||
|
||||
|
||||
@ -55,8 +58,9 @@ import { AdminRoutingModule } from './admin-routing.module';
|
||||
HomeComponent,
|
||||
BlogComponent,
|
||||
EventComponent,
|
||||
EventFormComponent
|
||||
|
||||
EventFormComponent,
|
||||
CareerComponent,
|
||||
EducationComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@ -17,84 +17,100 @@ import { EventComponent } from './component/event/event.component';
|
||||
import { BlogComponent } from './component/blog/blog.component';
|
||||
import { EventFormComponent } from './component/event-form/event-form.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
// {
|
||||
// path: '',
|
||||
// loadChildren: () =>
|
||||
// import('./pages/pages.module').then((m) => m.PagesModule),
|
||||
// },
|
||||
|
||||
export const routes: Routes = [
|
||||
// Dashboard/Admin routes with lazy loading
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () =>
|
||||
import('./admin/admin.module').then((m) => m.AdminModule),
|
||||
},
|
||||
|
||||
// Main application routes
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
|
||||
// Event routes - UNCOMMENTED AND FIXED
|
||||
{
|
||||
path: 'events',
|
||||
component: EventComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'eventForm',
|
||||
component: EventFormComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'eventForm/:id',
|
||||
component: EventFormComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'dashboard/eventForm',
|
||||
component: EventFormComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'dashboard/eventForm/:id',
|
||||
component: EventFormComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
|
||||
// Other routes
|
||||
{
|
||||
path: 'settings',
|
||||
component: SettingsComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
component: ProfileComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'blogs',
|
||||
component: BlogComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'user/management',
|
||||
component: UserComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
{
|
||||
path: 'professor/management',
|
||||
component: ProfessorComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
},
|
||||
|
||||
// Management routes with children
|
||||
{
|
||||
path: 'management',
|
||||
component: ManagementComponent,
|
||||
canActivate: [AuthenticationGuard],
|
||||
children: [
|
||||
{path: 'settings', component: SettingsComponent},
|
||||
{path: 'profile', component: ProfileComponent},
|
||||
{
|
||||
path: 'users',
|
||||
component: UsersComponent,
|
||||
children: [
|
||||
{path: ':id/view', component: UserViewComponent, resolve: {user: UserResolver}},
|
||||
{path: ':id/edit', component: UserEditComponent}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Default redirects
|
||||
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
|
||||
|
||||
// { path: 'home', component: HomeComponent },
|
||||
// { path: 'login', component: LoginComponent },
|
||||
// { path: 'register', component: RegisterComponent },
|
||||
// {
|
||||
// path: 'settings',
|
||||
// component: SettingsComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
// {
|
||||
// path: 'profile',
|
||||
// component: ProfileComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
// {
|
||||
// path: 'events',
|
||||
// component: EventComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
// {
|
||||
// path: 'eventForm',
|
||||
// component: EventFormComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
// {
|
||||
// path: 'eventForm/:id',
|
||||
// component: EventFormComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
// {
|
||||
// path: 'blogs',
|
||||
// component: BlogComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
// {
|
||||
// path: 'user/management',
|
||||
// component: UserComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
// {
|
||||
// path: 'professor/management',
|
||||
// component: ProfessorComponent,
|
||||
// canActivate: [AuthenticationGuard],
|
||||
// },
|
||||
|
||||
// {
|
||||
// path: 'management', component: ManagementComponent, canActivate: [AuthenticationGuard],
|
||||
// children: [
|
||||
// {path: 'settings', component: SettingsComponent},
|
||||
// {path: 'profile', component: ProfileComponent},
|
||||
// {
|
||||
// path: 'users', component: UsersComponent,
|
||||
// children: [
|
||||
// {path: ':id/view', component: UserViewComponent, resolve: {user: UserResolver}},
|
||||
// {path: ':id/edit', component: UserEditComponent}
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// { path: '', redirectTo: '/login', pathMatch: 'full' },
|
||||
{ path: '**', redirectTo: '/dashboard' }, // Wildcard route for 404 - redirects to dashboard
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
export class AppRoutingModule {}
|
||||
@ -1,5 +1,6 @@
|
||||
import {Compiler, NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
|
||||
import { HashLocationStrategy, LocationStrategy } from '@angular/common';
|
||||
|
||||
|
||||
@ -30,6 +31,8 @@ import { BlogService } from './service/blog.service';
|
||||
import { AngularEditorModule } from '@josipv/angular-editor-k2';
|
||||
import { NotificationModule } from './notification/notification.module';
|
||||
import { EventFormComponent } from './component/event-form/event-form.component';
|
||||
import { CareerComponent } from './component/career/career.component';
|
||||
import { EducationComponent } from './component/education/education.component';
|
||||
// import { PagesModule } from './pages/pages.module';
|
||||
|
||||
|
||||
@ -39,6 +42,8 @@ import { EventFormComponent } from './component/event-form/event-form.component'
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
// EducationComponent,
|
||||
// CareerComponent,
|
||||
// LoginComponent,
|
||||
// RegisterComponent,
|
||||
// UserComponent,
|
||||
|
||||
@ -11,13 +11,15 @@
|
||||
<div *ngIf="isShowForm" class="mb-4">
|
||||
<form [formGroup]="blogForm" (ngSubmit)="saveBlog()">
|
||||
<div class="container">
|
||||
<button type="submit" class="btn btn-primary mx-2">{{ editing ? 'Update Blog' : 'Create Blog' }}</button>
|
||||
<button type="submit" class="btn btn-primary mx-2">{{ editing ? 'Update Blog' : 'Create Blog'
|
||||
}}</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" (click)="resetForm()">Cancel</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-2 ">
|
||||
<label class="text-primary" for="title">Title</label>
|
||||
<input type="text" id="title" class="form-control" formControlName="title" placeholder="Enter blog title">
|
||||
<input type="text" id="title" class="form-control" formControlName="title"
|
||||
placeholder="Enter blog title">
|
||||
<div *ngIf="blogForm?.get('title')?.invalid && blogForm.get('title')?.touched" class="text-danger">
|
||||
Title is required.
|
||||
</div>
|
||||
@ -26,34 +28,62 @@
|
||||
<div class="form-group m-2">
|
||||
<label class="text-primary" for="professors">Professors</label>
|
||||
<select id="professors" formControlName="professors" class="form-control" multiple>
|
||||
<option *ngFor="let professor of allProfessors" [value]="professor.id">
|
||||
{{ professor.firstName }}
|
||||
</option>
|
||||
<option *ngFor="let professor of allProfessors" [value]="professor.id">
|
||||
{{ professor.firstName }}
|
||||
</option>
|
||||
</select>
|
||||
<div *ngIf="blogForm.get('professors')?.invalid && blogForm.get('professors')?.touched" class="text-danger mt-1">
|
||||
At least one professor must be selected.
|
||||
<div *ngIf="blogForm.get('professors')?.invalid && blogForm.get('professors')?.touched"
|
||||
class="text-danger mt-1">
|
||||
At least one professor must be selected.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-2">
|
||||
</div>
|
||||
|
||||
<div class="form-group m-2">
|
||||
<label class="text-primary" for="tags">Tags</label>
|
||||
<input type="text" id="tags" class="form-control" formControlName="tags" placeholder="Enter tags separated by commas">
|
||||
<input type="text" id="tags" class="form-control" formControlName="tags"
|
||||
placeholder="Enter tags separated by commas">
|
||||
<div *ngIf="blogForm.get('tags')?.invalid && blogForm.get('tags')?.touched" class="text-danger mt-1">
|
||||
Tags are required.
|
||||
Tags are required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-2">
|
||||
<input type="checkbox" class="mx-2" id="posted" formControlName="posted">
|
||||
<label class="text-primary" for="posted">Posted</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Add this section after the tags field and before the posted checkbox -->
|
||||
<div class="form-group m-2">
|
||||
<label class="text-primary" for="image">Blog Image</label>
|
||||
<input type="file" id="image" class="form-control" accept="image/*" (change)="onImageSelected($event)">
|
||||
|
||||
<!-- Image Preview -->
|
||||
<div *ngIf="imagePreviewUrl" class="mt-3">
|
||||
<img [src]="imagePreviewUrl" alt="Image preview" class="img-thumbnail"
|
||||
style="max-width: 300px; max-height: 200px;">
|
||||
<button type="button" class="btn btn-sm btn-danger ml-2"
|
||||
(click)="imagePreviewUrl = null; selectedImage = null;">
|
||||
Remove Image
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Upload Progress -->
|
||||
<div *ngIf="uploadingImage" class="mt-2">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 100%">
|
||||
Uploading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group m-2">
|
||||
<input type="checkbox" class="mx-2" id="posted" formControlName="posted">
|
||||
<label class="text-primary" for="posted">Posted</label>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group m-2">
|
||||
<label class="text-primary m-1" for="content">Content</label>
|
||||
<angular-editor [config]="editorConfig" [placeholder]="'Enter text here...'" formControlName="content"></angular-editor>
|
||||
<div *ngIf="blogForm.get('content')?.invalid && blogForm.get('content')?.touched" class="text-danger mt-1">
|
||||
<angular-editor [config]="editorConfig" [placeholder]="'Enter text here...'"
|
||||
formControlName="content"></angular-editor>
|
||||
<div *ngIf="blogForm.get('content')?.invalid && blogForm.get('content')?.touched"
|
||||
class="text-danger mt-1">
|
||||
Content is required.
|
||||
</div>
|
||||
</div>
|
||||
@ -69,50 +99,53 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Blog List -->
|
||||
<div *ngIf="!isShowForm">
|
||||
<div *ngIf="blogs.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<!-- <th>Content</th> -->
|
||||
<th>Authors</th>
|
||||
<th>Tags</th>
|
||||
<th>Posted</th> <!-- New column for posted status -->
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let blog of blogs">
|
||||
<td>{{ blog.title }}</td>
|
||||
<!-- <td>{{ blog.content | slice:0:50 }}...</td> -->
|
||||
<td>
|
||||
<span *ngFor="let professor of blog.professors">{{ professor.firstName }}<br></span>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngFor="let tag of blog.tags">{{ tag }}<br></span>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="blog.posted" class="text-success">✓</span> <!-- Check mark for posted -->
|
||||
<span *ngIf="!blog.posted" class="text-danger">✗</span> <!-- Cross mark for not posted -->
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm mr-2" (click)="editBlog(blog)"> <!-- Added margin-right for spacing -->
|
||||
Edit
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" (click)="deleteBlog(blog)">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div *ngIf="blogs.length === 0" class="alert alert-info">
|
||||
No blogs available. Please create a new blog.
|
||||
</div>
|
||||
</div>
|
||||
<!-- Blog List -->
|
||||
<div *ngIf="!isShowForm">
|
||||
<div *ngIf="blogs.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<!-- <th>Content</th> -->
|
||||
<th>Authors</th>
|
||||
<th>Tags</th>
|
||||
<th>Posted</th> <!-- New column for posted status -->
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let blog of blogs">
|
||||
<td>{{ blog.title }}</td>
|
||||
<!-- <td>{{ blog.content | slice:0:50 }}...</td> -->
|
||||
<td>
|
||||
<span *ngFor="let professor of blog.professors">{{ professor.firstName }}<br></span>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngFor="let tag of blog.tags">{{ tag }}<br></span>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="blog.posted" class="text-success">✓</span>
|
||||
<!-- Check mark for posted -->
|
||||
<span *ngIf="!blog.posted" class="text-danger">✗</span>
|
||||
<!-- Cross mark for not posted -->
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm mr-2" (click)="editBlog(blog)">
|
||||
<!-- Added margin-right for spacing -->
|
||||
Edit
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" (click)="deleteBlog(blog)">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="blogs.length === 0" class="alert alert-info">
|
||||
No blogs available. Please create a new blog.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AngularEditorConfig } from '@josipv/angular-editor-k2';
|
||||
import { AuthenticationService } from 'src/app/service/authentication.service';
|
||||
;
|
||||
import { BlogService } from 'src/app/service/blog.service';
|
||||
import { ProfessorService } from 'src/app/service/professor.service';
|
||||
|
||||
@ -18,9 +17,14 @@ export class BlogComponent implements OnInit {
|
||||
blogForm: FormGroup;
|
||||
editing: boolean = false;
|
||||
loggedInUser: any;
|
||||
currentBlog: any = null; // Holds the blog being edited
|
||||
isShowForm = false; // Controls visibility of form vs. table
|
||||
currentBlog: any = null;
|
||||
isShowForm = false;
|
||||
content = '';
|
||||
|
||||
// Image upload properties
|
||||
selectedImage: File | null = null;
|
||||
imagePreviewUrl: string | null = null;
|
||||
uploadingImage: boolean = false;
|
||||
|
||||
constructor(
|
||||
private blogService: BlogService,
|
||||
@ -32,9 +36,9 @@ export class BlogComponent implements OnInit {
|
||||
this.blogForm = this.fb.group({
|
||||
title: ['', Validators.required],
|
||||
content: ['', Validators.required],
|
||||
professors: [[], Validators.required], // To hold selected professor IDs
|
||||
tags: ['', Validators.required], // To hold tags as a comma-separated string
|
||||
posted: [true], // Initialize checkbox with default value false
|
||||
professors: [[], Validators.required],
|
||||
tags: ['', Validators.required],
|
||||
posted: [true],
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,9 +57,6 @@ export class BlogComponent implements OnInit {
|
||||
defaultParagraphSeparator: '',
|
||||
defaultFontName: '',
|
||||
defaultFontSize: '',
|
||||
// headers: [{
|
||||
|
||||
// }],
|
||||
fonts: [
|
||||
{ class: 'arial', name: 'Arial' },
|
||||
{ class: 'times-new-roman', name: 'Times New Roman' },
|
||||
@ -77,111 +78,236 @@ export class BlogComponent implements OnInit {
|
||||
tag: 'h1',
|
||||
},
|
||||
],
|
||||
// uploadUrl: 'v1/image',
|
||||
// upload: (file: File) => { ... }
|
||||
// uploadWithCredentials: false,
|
||||
sanitize: true,
|
||||
toolbarPosition: 'top',
|
||||
toolbarHiddenButtons: [['bold', 'italic'], ['fontSize']],
|
||||
};
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loggedInUser = this.authService.getUserFromLocalStorage();
|
||||
this.professorService
|
||||
.getAllProfessors()
|
||||
.subscribe((res) => (this.allProfessors = res.content));
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loggedInUser = this.authService.getUserFromLocalStorage();
|
||||
this.professorService
|
||||
.getAllProfessors()
|
||||
.subscribe((res) => (this.allProfessors = res.content));
|
||||
this.loadBlogs();
|
||||
|
||||
// Subscribe to form value changes to update content for preview
|
||||
this.blogForm.get('content')?.valueChanges.subscribe((value) => {
|
||||
this.content = value;
|
||||
});
|
||||
}
|
||||
|
||||
this.loadBlogs();
|
||||
// Subscribe to form value changes to update content for preview
|
||||
this.blogForm.get('content')?.valueChanges.subscribe((value) => {
|
||||
this.content = value;
|
||||
});
|
||||
}
|
||||
showForm() {
|
||||
this.isShowForm = true;
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
showForm() {
|
||||
this.isShowForm = true;
|
||||
this.resetForm(); // Ensure form is reset when showing
|
||||
showTable() {
|
||||
this.isShowForm = false;
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
loadBlogs() {
|
||||
this.blogService.getBlogs().subscribe(data => {
|
||||
this.blogs = data;
|
||||
});
|
||||
}
|
||||
|
||||
createBlog() {
|
||||
this.showForm();
|
||||
this.blogForm.reset();
|
||||
this.selectedBlog = null;
|
||||
this.editing = false;
|
||||
}
|
||||
|
||||
editBlog(blog: any) {
|
||||
this.selectedBlog = blog;
|
||||
|
||||
this.blogForm.patchValue({
|
||||
title: blog.title,
|
||||
content: blog.content,
|
||||
posted: blog.posted,
|
||||
professors: blog.professors.map((prof: any) => prof.id),
|
||||
tags: blog.tags.join(', ')
|
||||
});
|
||||
|
||||
// Set image preview if exists
|
||||
if (blog.imageUrl) {
|
||||
this.imagePreviewUrl = blog.imageUrl;
|
||||
}
|
||||
|
||||
showTable() {
|
||||
this.isShowForm = false;
|
||||
this.resetForm(); // Ensure form is reset when switching back
|
||||
}
|
||||
|
||||
loadBlogs() {
|
||||
this.blogService.getBlogs().subscribe(data => {
|
||||
this.blogs = data;
|
||||
});
|
||||
}
|
||||
|
||||
createBlog() {
|
||||
this.showForm(); // Show form to create a new blog
|
||||
this.blogForm.reset();
|
||||
this.selectedBlog = null;
|
||||
this.editing = false;
|
||||
}
|
||||
|
||||
editBlog(blog: any) {
|
||||
|
||||
this.selectedBlog = blog;
|
||||
|
||||
this.blogForm.patchValue({
|
||||
title: blog.title,
|
||||
content: blog.content,
|
||||
posted: blog.posted,
|
||||
professors: blog.professors.map((prof: any) => prof.id), // Assuming professors are an array of objects
|
||||
tags: blog.tags.join(', ') // Convert tags array back to comma-separated string
|
||||
});
|
||||
this.editing = true;
|
||||
this.isShowForm = true;
|
||||
|
||||
this.editing = true;
|
||||
this.isShowForm = true;
|
||||
}
|
||||
|
||||
// Image upload methods
|
||||
onImageSelected(event: any) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
// Validate file type
|
||||
if (!file.type.startsWith('image/')) {
|
||||
alert('Please select a valid image file.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file size (10MB max)
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB in bytes
|
||||
if (file.size > maxSize) {
|
||||
alert('File size must be less than 10MB.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedImage = file;
|
||||
|
||||
// Create preview URL
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: any) => {
|
||||
this.imagePreviewUrl = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
saveBlog() {
|
||||
if (this.blogForm.valid) {
|
||||
const blogData = this.blogForm.value;
|
||||
|
||||
// Convert tags to array and professors to array of IDs
|
||||
blogData.tags = blogData.tags.split(',').map((tag: string) => tag.trim());
|
||||
blogData.professors = blogData.professors || [];
|
||||
|
||||
blogData.author = this.loggedInUser._id; // Associate logged-in user with the blog
|
||||
|
||||
if (this.editing && this.selectedBlog) {
|
||||
this.blogService.updateBlog(this.selectedBlog.id, blogData).subscribe(() => {
|
||||
this.loadBlogs();
|
||||
this.resetForm();
|
||||
this.isShowForm = false; // Hide form after update
|
||||
});
|
||||
} else {
|
||||
this.blogService.createBlog(blogData).subscribe(() => {
|
||||
this.loadBlogs();
|
||||
this.resetForm();
|
||||
this.isShowForm = false; // Hide form after creation
|
||||
});
|
||||
}
|
||||
|
||||
removeImage() {
|
||||
this.selectedImage = null;
|
||||
this.imagePreviewUrl = null;
|
||||
|
||||
// Clear the file input
|
||||
const fileInput = document.getElementById('image') as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async uploadImage(): Promise<string | null> {
|
||||
if (!this.selectedImage) return null;
|
||||
|
||||
this.uploadingImage = true;
|
||||
try {
|
||||
const response = await this.blogService.uploadImage(this.selectedImage).toPromise();
|
||||
return response.url;
|
||||
} catch (error) {
|
||||
console.error('Error uploading image:', error);
|
||||
alert('Failed to upload image. Please try again.');
|
||||
return null;
|
||||
} finally {
|
||||
this.uploadingImage = false;
|
||||
}
|
||||
}
|
||||
|
||||
async saveBlog() {
|
||||
if (this.blogForm.valid) {
|
||||
let imageUrl = null;
|
||||
|
||||
// Upload image if selected
|
||||
if (this.selectedImage) {
|
||||
imageUrl = await this.uploadImage();
|
||||
if (!imageUrl) {
|
||||
return; // Stop if image upload failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteBlog(blog: any) {
|
||||
if (confirm('Are you sure you want to delete this blog?')) {
|
||||
this.blogService.deleteBlog(blog.id).subscribe(() => {
|
||||
this.loadBlogs();
|
||||
});
|
||||
|
||||
const blogData = this.blogForm.value;
|
||||
|
||||
// Convert tags to array and professors to array of IDs
|
||||
blogData.tags = blogData.tags.split(',').map((tag: string) => tag.trim());
|
||||
blogData.professors = blogData.professors || [];
|
||||
|
||||
// Add image URL if uploaded, or keep existing image for updates
|
||||
if (imageUrl) {
|
||||
blogData.imageUrl = imageUrl;
|
||||
} else if (this.editing && this.selectedBlog && this.selectedBlog.imageUrl && this.imagePreviewUrl) {
|
||||
// Keep existing image URL if editing and no new image selected
|
||||
blogData.imageUrl = this.selectedBlog.imageUrl;
|
||||
}
|
||||
|
||||
blogData.author = this.loggedInUser._id;
|
||||
|
||||
if (this.editing && this.selectedBlog) {
|
||||
this.blogService.updateBlog(this.selectedBlog.id, blogData).subscribe({
|
||||
next: () => {
|
||||
this.loadBlogs();
|
||||
this.resetForm();
|
||||
this.isShowForm = false;
|
||||
alert('Blog updated successfully!');
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error updating blog:', error);
|
||||
alert('Failed to update blog. Please try again.');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.blogService.createBlog(blogData).subscribe({
|
||||
next: () => {
|
||||
this.loadBlogs();
|
||||
this.resetForm();
|
||||
this.isShowForm = false;
|
||||
alert('Blog created successfully!');
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error creating blog:', error);
|
||||
alert('Failed to create blog. Please try again.');
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Mark all fields as touched to show validation errors
|
||||
Object.keys(this.blogForm.controls).forEach(key => {
|
||||
this.blogForm.get(key)?.markAsTouched();
|
||||
});
|
||||
alert('Please fill in all required fields.');
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.blogForm.reset();
|
||||
this.selectedBlog = null;
|
||||
this.editing = false;
|
||||
this.blogForm.patchValue({
|
||||
professors: [],
|
||||
tags: ''
|
||||
}
|
||||
|
||||
deleteBlog(blog: any) {
|
||||
if (confirm('Are you sure you want to delete this blog?')) {
|
||||
this.blogService.deleteBlog(blog.id).subscribe({
|
||||
next: () => {
|
||||
this.loadBlogs();
|
||||
alert('Blog deleted successfully!');
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error deleting blog:', error);
|
||||
alert('Failed to delete blog. Please try again.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resetForm() {
|
||||
this.blogForm.reset();
|
||||
this.selectedBlog = null;
|
||||
this.editing = false;
|
||||
this.selectedImage = null;
|
||||
this.imagePreviewUrl = null;
|
||||
|
||||
this.blogForm.patchValue({
|
||||
professors: [],
|
||||
tags: '',
|
||||
posted: true
|
||||
});
|
||||
|
||||
// Clear the file input
|
||||
const fileInput = document.getElementById('image') as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Utility method to check if form field has error
|
||||
hasError(fieldName: string): boolean {
|
||||
const field = this.blogForm.get(fieldName);
|
||||
return !!(field && field.invalid && field.touched);
|
||||
}
|
||||
|
||||
// Utility method to get error message for a field
|
||||
getErrorMessage(fieldName: string): string {
|
||||
const field = this.blogForm.get(fieldName);
|
||||
if (field && field.errors && field.touched) {
|
||||
if (field.errors['required']) {
|
||||
return `${fieldName} is required.`;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,248 @@
|
||||
<app-menu></app-menu>
|
||||
<div class="container mt-4">
|
||||
<!-- Header Actions -->
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<div>
|
||||
<button *ngIf="!showJobForm && !showApplications" class="btn btn-primary" (click)="showJobFormModal()">
|
||||
<i class="fa fa-plus"></i> New Job
|
||||
</button>
|
||||
<button *ngIf="showJobForm" class="btn btn-secondary" (click)="hideJobForm()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Jobs
|
||||
</button>
|
||||
<button *ngIf="showApplications" class="btn btn-secondary" (click)="hideApplications()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Jobs
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!showJobForm && !showApplications">
|
||||
<span class="badge badge-info">Total Jobs: {{ jobs.length }}</span>
|
||||
<span class="badge badge-warning ml-2">Total Applications: {{ applications.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Job Form -->
|
||||
<div *ngIf="showJobForm" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{{ editing ? 'Edit Job' : 'Create New Job' }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form [formGroup]="jobForm" (ngSubmit)="saveJob()">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="title" class="text-primary">Job Title *</label>
|
||||
<input type="text" id="title" class="form-control" formControlName="title" placeholder="Enter job title">
|
||||
<div *ngIf="jobForm.get('title')?.invalid && jobForm.get('title')?.touched" class="text-danger">
|
||||
Job title is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="department" class="text-primary">Department *</label>
|
||||
<input type="text" id="department" class="form-control" formControlName="department" placeholder="Enter department">
|
||||
<div *ngIf="jobForm.get('department')?.invalid && jobForm.get('department')?.touched" class="text-danger">
|
||||
Department is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="location" class="text-primary">Location *</label>
|
||||
<input type="text" id="location" class="form-control" formControlName="location" placeholder="Enter location">
|
||||
<div *ngIf="jobForm.get('location')?.invalid && jobForm.get('location')?.touched" class="text-danger">
|
||||
Location is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="type" class="text-primary">Job Type *</label>
|
||||
<select id="type" class="form-control" formControlName="type">
|
||||
<option value="">Select job type</option>
|
||||
<option value="Full-time">Full-time</option>
|
||||
<option value="Part-time">Part-time</option>
|
||||
<option value="Contract">Contract</option>
|
||||
<option value="Rotational">Rotational</option>
|
||||
<option value="Observership">Observership</option>
|
||||
</select>
|
||||
<div *ngIf="jobForm.get('type')?.invalid && jobForm.get('type')?.touched" class="text-danger">
|
||||
Job type is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="experience" class="text-primary">Experience Required *</label>
|
||||
<input type="text" id="experience" class="form-control" formControlName="experience" placeholder="e.g., MBBS + MS preferred">
|
||||
<div *ngIf="jobForm.get('experience')?.invalid && jobForm.get('experience')?.touched" class="text-danger">
|
||||
Experience requirement is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="salary" class="text-primary">Salary *</label>
|
||||
<input type="text" id="salary" class="form-control" formControlName="salary" placeholder="e.g., As per hospital norms">
|
||||
<div *ngIf="jobForm.get('salary')?.invalid && jobForm.get('salary')?.touched" class="text-danger">
|
||||
Salary information is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description" class="text-primary">Job Description *</label>
|
||||
<textarea id="description" class="form-control" formControlName="description" rows="4" placeholder="Enter job description"></textarea>
|
||||
<div *ngIf="jobForm.get('description')?.invalid && jobForm.get('description')?.touched" class="text-danger">
|
||||
Job description is required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="requirements" class="text-primary">Requirements</label>
|
||||
<textarea id="requirements" class="form-control" formControlName="requirements" rows="3" placeholder="Enter requirements separated by commas"></textarea>
|
||||
<small class="form-text text-muted">Separate multiple requirements with commas</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="responsibilities" class="text-primary">Responsibilities</label>
|
||||
<textarea id="responsibilities" class="form-control" formControlName="responsibilities" rows="3" placeholder="Enter responsibilities separated by commas"></textarea>
|
||||
<small class="form-text text-muted">Separate multiple responsibilities with commas</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="isActive" formControlName="isActive">
|
||||
<label class="form-check-label text-primary" for="isActive">Active (visible to applicants)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" [disabled]="jobForm.invalid">
|
||||
<i class="fa fa-save"></i> {{ editing ? 'Update Job' : 'Create Job' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" (click)="resetJobForm()">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Applications View -->
|
||||
<div *ngIf="showApplications && selectedJobForApplications" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Applications for: {{ selectedJobForApplications.title }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div *ngIf="applications.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Applicant</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Experience</th>
|
||||
<th>Status</th>
|
||||
<th>Applied Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let application of applications">
|
||||
<td>{{ application.fullName }}</td>
|
||||
<td>{{ application.email }}</td>
|
||||
<td>{{ application.phone }}</td>
|
||||
<td>{{ application.experience }}</td>
|
||||
<td>
|
||||
<span class="badge" [ngClass]="getStatusBadgeClass(application.status || 'pending')">
|
||||
{{ application.status || 'PENDING' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ application.createdDate | date:'short' }}</td>
|
||||
<td>
|
||||
<div class="btn-group" dropdown>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle" data-toggle="dropdown">
|
||||
Status
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" (click)="updateApplicationStatus(application, 'PENDING')">Pending</a>
|
||||
<a class="dropdown-item" (click)="updateApplicationStatus(application, 'REVIEWED')">Reviewed</a>
|
||||
<a class="dropdown-item" (click)="updateApplicationStatus(application, 'SHORTLISTED')">Shortlisted</a>
|
||||
<a class="dropdown-item" (click)="updateApplicationStatus(application, 'INTERVIEWED')">Interviewed</a>
|
||||
<a class="dropdown-item" (click)="updateApplicationStatus(application, 'HIRED')">Hired</a>
|
||||
<a class="dropdown-item" (click)="updateApplicationStatus(application, 'REJECTED')">Rejected</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger ml-1" (click)="deleteApplication(application)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="applications.length === 0" class="alert alert-info">
|
||||
No applications found for this job.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jobs List -->
|
||||
<div *ngIf="!showJobForm && !showApplications">
|
||||
<div *ngIf="jobs.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Department</th>
|
||||
<th>Location</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>Applications</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let job of jobs">
|
||||
<td>
|
||||
<strong>{{ job.title }}</strong>
|
||||
<br>
|
||||
<small class="text-muted">{{ job.experience }}</small>
|
||||
</td>
|
||||
<td>{{ job.department }}</td>
|
||||
<td>{{ job.location }}</td>
|
||||
<td>
|
||||
<span class="badge badge-secondary">{{ job.type }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="job.isActive" class="badge badge-success">Active</span>
|
||||
<span *ngIf="!job.isActive" class="badge badge-danger">Inactive</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" (click)="viewApplications(job)">
|
||||
View Applications
|
||||
<span class="badge badge-light ml-1">{{ getApplicationCount(job.id) }}</span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm mr-2" (click)="editJob(job)">
|
||||
<i class="fa fa-edit"></i> Edit
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" (click)="deleteJob(job)">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="jobs.length === 0" class="alert alert-info">
|
||||
No jobs available. Please create a new job.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CareerComponent } from './career.component';
|
||||
|
||||
describe('CareerComponent', () => {
|
||||
let component: CareerComponent;
|
||||
let fixture: ComponentFixture<CareerComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CareerComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CareerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,205 @@
|
||||
// career.component.ts
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { CareerService, Job, JobApplication } from 'src/app/service/career.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-career',
|
||||
templateUrl: './career.component.html',
|
||||
styleUrls: ['./career.component.css'],
|
||||
})
|
||||
export class CareerComponent implements OnInit {
|
||||
jobs: Job[] = [];
|
||||
applications: JobApplication[] = [];
|
||||
selectedJob: Job | null = null;
|
||||
jobForm: FormGroup;
|
||||
showJobForm = false;
|
||||
editing = false;
|
||||
showApplications = false;
|
||||
|
||||
// Filter for applications
|
||||
selectedJobForApplications: Job | null = null;
|
||||
|
||||
constructor(
|
||||
private careerService: CareerService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.jobForm = this.fb.group({
|
||||
title: ['', Validators.required],
|
||||
department: ['', Validators.required],
|
||||
location: ['', Validators.required],
|
||||
type: ['', Validators.required],
|
||||
experience: ['', Validators.required],
|
||||
salary: ['', Validators.required],
|
||||
description: ['', Validators.required],
|
||||
requirements: [''],
|
||||
responsibilities: [''],
|
||||
isActive: [true] // Default to true
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadJobs();
|
||||
this.loadApplications();
|
||||
}
|
||||
|
||||
loadJobs() {
|
||||
this.careerService.getAllJobs().subscribe(data => {
|
||||
this.jobs = data;
|
||||
});
|
||||
}
|
||||
|
||||
loadApplications() {
|
||||
this.careerService.getAllApplications().subscribe(data => {
|
||||
this.applications = data;
|
||||
});
|
||||
}
|
||||
|
||||
showJobFormModal() {
|
||||
this.showJobForm = true;
|
||||
this.resetJobForm();
|
||||
|
||||
// Ensure isActive is true for new jobs
|
||||
setTimeout(() => {
|
||||
this.jobForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
hideJobForm() {
|
||||
this.showJobForm = false;
|
||||
this.resetJobForm();
|
||||
}
|
||||
|
||||
editJob(job: Job) {
|
||||
this.selectedJob = job;
|
||||
this.jobForm.patchValue({
|
||||
title: job.title,
|
||||
department: job.department,
|
||||
location: job.location,
|
||||
type: job.type,
|
||||
experience: job.experience,
|
||||
salary: job.salary,
|
||||
description: job.description,
|
||||
requirements: job.requirements ? job.requirements.join(', ') : '',
|
||||
responsibilities: job.responsibilities ? job.responsibilities.join(', ') : '',
|
||||
isActive: job.isActive // This will use the actual job's active status
|
||||
});
|
||||
this.editing = true;
|
||||
this.showJobForm = true;
|
||||
}
|
||||
|
||||
saveJob() {
|
||||
if (this.jobForm.valid) {
|
||||
const jobData = this.jobForm.value;
|
||||
|
||||
console.log('=== ANGULAR DEBUG ===');
|
||||
console.log('Form value:', this.jobForm.value);
|
||||
console.log('isActive form control value:', this.jobForm.get('isActive')?.value);
|
||||
console.log('isActive in jobData:', jobData.isActive);
|
||||
console.log('Type of isActive:', typeof jobData.isActive);
|
||||
|
||||
// Ensure isActive is properly set as boolean
|
||||
jobData.isActive = this.jobForm.get('isActive')?.value === true;
|
||||
|
||||
console.log('isActive after boolean conversion:', jobData.isActive);
|
||||
|
||||
// Convert comma-separated strings to arrays
|
||||
jobData.requirements = jobData.requirements
|
||||
? jobData.requirements.split(',').map((req: string) => req.trim()).filter((req: string) => req.length > 0)
|
||||
: [];
|
||||
jobData.responsibilities = jobData.responsibilities
|
||||
? jobData.responsibilities.split(',').map((resp: string) => resp.trim()).filter((resp: string) => resp.length > 0)
|
||||
: [];
|
||||
|
||||
console.log('Final jobData being sent:', jobData);
|
||||
|
||||
if (this.editing && this.selectedJob) {
|
||||
this.careerService.updateJob(this.selectedJob.id!, jobData).subscribe(() => {
|
||||
this.loadJobs();
|
||||
this.hideJobForm();
|
||||
});
|
||||
} else {
|
||||
this.careerService.createJob(jobData).subscribe(() => {
|
||||
this.loadJobs();
|
||||
this.hideJobForm();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteJob(job: Job) {
|
||||
if (confirm('Are you sure you want to delete this job?')) {
|
||||
this.careerService.deleteJob(job.id!).subscribe(() => {
|
||||
this.loadJobs();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetJobForm() {
|
||||
this.jobForm.reset();
|
||||
this.selectedJob = null;
|
||||
this.editing = false;
|
||||
|
||||
// Explicitly set default values after reset
|
||||
this.jobForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}
|
||||
|
||||
// Application management
|
||||
viewApplications(job: Job) {
|
||||
this.selectedJobForApplications = job;
|
||||
this.showApplications = true;
|
||||
this.careerService.getApplicationsByJobId(job.id!).subscribe(data => {
|
||||
this.applications = data;
|
||||
});
|
||||
}
|
||||
|
||||
hideApplications() {
|
||||
this.showApplications = false;
|
||||
this.selectedJobForApplications = null;
|
||||
this.loadApplications(); // Load all applications
|
||||
}
|
||||
|
||||
updateApplicationStatus(application: JobApplication, status: string) {
|
||||
this.careerService.updateApplicationStatus(application.id!, status).subscribe(() => {
|
||||
// Reload applications for the selected job
|
||||
if (this.selectedJobForApplications) {
|
||||
this.viewApplications(this.selectedJobForApplications);
|
||||
} else {
|
||||
this.loadApplications();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteApplication(application: JobApplication) {
|
||||
if (confirm('Are you sure you want to delete this application?')) {
|
||||
this.careerService.deleteApplication(application.id!).subscribe(() => {
|
||||
if (this.selectedJobForApplications) {
|
||||
this.viewApplications(this.selectedJobForApplications);
|
||||
} else {
|
||||
this.loadApplications();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatusBadgeClass(status: string): string {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'pending': return 'badge-warning';
|
||||
case 'reviewed': return 'badge-info';
|
||||
case 'shortlisted': return 'badge-primary';
|
||||
case 'interviewed': return 'badge-secondary';
|
||||
case 'hired': return 'badge-success';
|
||||
case 'rejected': return 'badge-danger';
|
||||
default: return 'badge-light';
|
||||
}
|
||||
}
|
||||
|
||||
getApplicationCount(jobId?: number): number {
|
||||
if (!jobId) return 0;
|
||||
return this.applications.filter(app => app.job?.id === jobId).length;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,481 @@
|
||||
<div class="container mt-4">
|
||||
<!-- Header -->
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h2 class="text-primary">Education & Training Management</h2>
|
||||
<p class="text-muted">Manage courses, programs, upcoming events and applications</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header Actions -->
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<div>
|
||||
<button *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm"
|
||||
class="btn btn-primary mr-2" (click)="showCourseFormModal()">
|
||||
<i class="fa fa-plus"></i> New Course
|
||||
</button>
|
||||
<button *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm"
|
||||
class="btn btn-info" (click)="showUpcomingEventsModal()">
|
||||
<i class="fa fa-calendar"></i> Manage Upcoming Events
|
||||
</button>
|
||||
|
||||
<button *ngIf="showCourseForm" class="btn btn-secondary" (click)="hideCourseForm()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Courses
|
||||
</button>
|
||||
<button *ngIf="showApplications" class="btn btn-secondary" (click)="hideApplications()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Courses
|
||||
</button>
|
||||
<button *ngIf="showUpcomingEvents && !showUpcomingEventForm" class="btn btn-secondary mr-2"
|
||||
(click)="hideUpcomingEventsModal()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Courses
|
||||
</button>
|
||||
<button *ngIf="showUpcomingEvents && !showUpcomingEventForm" class="btn btn-primary"
|
||||
(click)="showUpcomingEventFormModal()">
|
||||
<i class="fa fa-plus"></i> New Event
|
||||
</button>
|
||||
<button *ngIf="showUpcomingEventForm" class="btn btn-secondary" (click)="hideUpcomingEventForm()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Events
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm">
|
||||
<span class="badge badge-info">Total Courses: {{ courses.length }}</span>
|
||||
<span class="badge badge-warning ml-2">Total Applications: {{ applications.length }}</span>
|
||||
<span class="badge badge-success ml-2">Upcoming Events: {{ upcomingEvents.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upcoming Event Form -->
|
||||
<div *ngIf="showUpcomingEventForm" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{{ editingUpcomingEvent ? 'Edit Upcoming Event' : 'Create New Upcoming Event' }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form [formGroup]="upcomingEventForm" (ngSubmit)="saveUpcomingEvent()">
|
||||
<div class="form-group">
|
||||
<label for="eventTitle" class="text-primary">Event Title *</label>
|
||||
<input type="text" id="eventTitle" class="form-control" formControlName="title"
|
||||
placeholder="Enter event title">
|
||||
<div *ngIf="upcomingEventForm.get('title')?.invalid && upcomingEventForm.get('title')?.touched"
|
||||
class="text-danger">
|
||||
Event title is required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="eventDescription" class="text-primary">Event Description *</label>
|
||||
<textarea id="eventDescription" class="form-control" formControlName="description" rows="4"
|
||||
placeholder="Enter event description"></textarea>
|
||||
<div *ngIf="upcomingEventForm.get('description')?.invalid && upcomingEventForm.get('description')?.touched"
|
||||
class="text-danger">
|
||||
Event description is required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="eventSchedule" class="text-primary">Schedule *</label>
|
||||
<input type="text" id="eventSchedule" class="form-control" formControlName="schedule"
|
||||
placeholder="e.g., Q3 2025, Monthly Sessions, Ongoing">
|
||||
<div *ngIf="upcomingEventForm.get('schedule')?.invalid && upcomingEventForm.get('schedule')?.touched"
|
||||
class="text-danger">
|
||||
Schedule is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="eventDate" class="text-primary">Event Date (Optional)</label>
|
||||
<input type="date" id="eventDate" class="form-control" formControlName="eventDate">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="eventIsActive" formControlName="isActive">
|
||||
<label class="form-check-label text-primary" for="eventIsActive">Active (visible on website)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" [disabled]="upcomingEventForm.invalid">
|
||||
<i class="fa fa-save"></i> {{ editingUpcomingEvent ? 'Update Event' : 'Create Event' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" (click)="resetUpcomingEventForm()">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upcoming Events List -->
|
||||
<div *ngIf="showUpcomingEvents && !showUpcomingEventForm">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Upcoming Events Management</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div *ngIf="upcomingEvents.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Description</th>
|
||||
<th>Schedule</th>
|
||||
<th>Event Date</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let event of upcomingEvents">
|
||||
<td>
|
||||
<strong>{{ event.title }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span [title]="event.description">
|
||||
{{ event.description.length > 100 ? (event.description | slice:0:100) + '...' :
|
||||
event.description }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ event.schedule }}</td>
|
||||
<td>{{ event.eventDate ? (event.eventDate | date:'short') : 'N/A' }}</td>
|
||||
<td>
|
||||
<span *ngIf="event.isActive" class="badge badge-success">Active</span>
|
||||
<span *ngIf="!event.isActive" class="badge badge-danger">Inactive</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm mr-2" (click)="editUpcomingEvent(event)">
|
||||
<i class="fa fa-edit"></i> Edit
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" (click)="deleteUpcomingEvent(event)">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="upcomingEvents.length === 0" class="alert alert-info">
|
||||
No upcoming events available. Please create a new event.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Course Form (existing content) -->
|
||||
<div *ngIf="showCourseForm" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{{ editing ? 'Edit Course' : 'Create New Course' }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form [formGroup]="courseForm" (ngSubmit)="saveCourse()">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="title" class="text-primary">Course Title *</label>
|
||||
<input type="text" id="title" class="form-control" formControlName="title"
|
||||
placeholder="Enter course title">
|
||||
<div *ngIf="courseForm.get('title')?.invalid && courseForm.get('title')?.touched"
|
||||
class="text-danger">
|
||||
Course title is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="instructor" class="text-primary">Instructor *</label>
|
||||
<input type="text" id="instructor" class="form-control" formControlName="instructor"
|
||||
placeholder="Enter instructor name">
|
||||
<div *ngIf="courseForm.get('instructor')?.invalid && courseForm.get('instructor')?.touched"
|
||||
class="text-danger">
|
||||
Instructor is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description" class="text-primary">Course Description *</label>
|
||||
<textarea id="description" class="form-control" formControlName="description" rows="4"
|
||||
placeholder="Enter course description"></textarea>
|
||||
<div *ngIf="courseForm.get('description')?.invalid && courseForm.get('description')?.touched"
|
||||
class="text-danger">
|
||||
Course description is required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="duration" class="text-primary">Duration *</label>
|
||||
<input type="text" id="duration" class="form-control" formControlName="duration"
|
||||
placeholder="e.g., 3 Days, 2 Years">
|
||||
<div *ngIf="courseForm.get('duration')?.invalid && courseForm.get('duration')?.touched"
|
||||
class="text-danger">
|
||||
Duration is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="seats" class="text-primary">Number of Seats *</label>
|
||||
<input type="number" id="seats" class="form-control" formControlName="seats"
|
||||
placeholder="Enter number of seats" min="1">
|
||||
<div *ngIf="courseForm.get('seats')?.invalid && courseForm.get('seats')?.touched"
|
||||
class="text-danger">
|
||||
Number of seats is required and must be at least 1.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="price" class="text-primary">Price</label>
|
||||
<input type="text" id="price" class="form-control" formControlName="price"
|
||||
placeholder="e.g., $15,000 or N/A">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="category" class="text-primary">Category *</label>
|
||||
<select id="category" class="form-control" formControlName="category">
|
||||
<option value="">Select category</option>
|
||||
<option value="Certification">Certification</option>
|
||||
<option value="Training">Training</option>
|
||||
<option value="Workshop">Workshop</option>
|
||||
<option value="Fellowship">Fellowship</option>
|
||||
<option value="Course">Course</option>
|
||||
</select>
|
||||
<div *ngIf="courseForm.get('category')?.invalid && courseForm.get('category')?.touched"
|
||||
class="text-danger">
|
||||
Category is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="level" class="text-primary">Level *</label>
|
||||
<select id="level" class="form-control" formControlName="level">
|
||||
<option value="">Select level</option>
|
||||
<option value="Beginner">Beginner</option>
|
||||
<option value="Intermediate">Intermediate</option>
|
||||
<option value="Advanced">Advanced</option>
|
||||
<option value="Professional">Professional</option>
|
||||
<option value="Post-Doctoral">Post-Doctoral</option>
|
||||
</select>
|
||||
<div *ngIf="courseForm.get('level')?.invalid && courseForm.get('level')?.touched"
|
||||
class="text-danger">
|
||||
Level is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="startDate" class="text-primary">Start Date</label>
|
||||
<input type="date" id="startDate" class="form-control" formControlName="startDate">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Upload Section -->
|
||||
<div class="form-group">
|
||||
<label class="text-primary">Course Image</label>
|
||||
|
||||
<!-- Image Preview -->
|
||||
<div *ngIf="imagePreview" class="mb-3">
|
||||
<div class="position-relative d-inline-block">
|
||||
<img [src]="getFullImageUrl(imagePreview)" alt="Preview" class="img-thumbnail"
|
||||
style="max-width: 300px; max-height: 200px;">
|
||||
<button type="button" class="btn btn-danger btn-sm position-absolute"
|
||||
style="top: -5px; right: -5px;" (click)="removeImage()">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File Input -->
|
||||
<div class="mb-2">
|
||||
<input type="file" id="imageInput" class="form-control" accept="image/*"
|
||||
(change)="onImageSelected($event)">
|
||||
<small class="form-text text-muted">Supported formats: JPG, PNG, GIF. Max size: 5MB</small>
|
||||
</div>
|
||||
|
||||
<!-- Upload Error -->
|
||||
<div *ngIf="uploadError" class="alert alert-danger alert-sm">
|
||||
{{ uploadError }}
|
||||
</div>
|
||||
|
||||
<!-- Upload Progress -->
|
||||
<div *ngIf="isImageUploading" class="mb-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<small class="text-muted">Uploading image...</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="eligibility" class="text-primary">Eligibility Criteria</label>
|
||||
<textarea id="eligibility" class="form-control" formControlName="eligibility" rows="3"
|
||||
placeholder="Enter eligibility criteria separated by commas"></textarea>
|
||||
<small class="form-text text-muted">Separate multiple criteria with commas</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="objectives" class="text-primary">Learning Objectives</label>
|
||||
<textarea id="objectives" class="form-control" formControlName="objectives" rows="3"
|
||||
placeholder="Enter learning objectives separated by commas"></textarea>
|
||||
<small class="form-text text-muted">Separate multiple objectives with commas</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="isActive" formControlName="isActive">
|
||||
<label class="form-check-label text-primary" for="isActive">Active (visible to students)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" [disabled]="courseForm.invalid || isImageUploading">
|
||||
<i class="fa fa-save"></i> {{ editing ? 'Update Course' : 'Create Course' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" (click)="resetCourseForm()">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Applications View (existing content) -->
|
||||
<div *ngIf="showApplications && selectedCourseForApplications" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Applications for: {{ selectedCourseForApplications.title }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div *ngIf="applications.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Applicant</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Qualification</th>
|
||||
<th>Experience</th>
|
||||
<th>Status</th>
|
||||
<th>Applied Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let application of applications">
|
||||
<td>{{ application.fullName }}</td>
|
||||
<td>{{ application.email }}</td>
|
||||
<td>{{ application.phone }}</td>
|
||||
<td>{{ application.qualification }}</td>
|
||||
<td>{{ application.experience || 'N/A' }}</td>
|
||||
<td>
|
||||
<span class="badge" [ngClass]="getStatusBadgeClass(application.status || 'pending')">
|
||||
{{ application.status || 'PENDING' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ application.createdDate | date:'short' }}</td>
|
||||
<td>
|
||||
<div class="btn-group dropdown">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle"
|
||||
data-toggle="dropdown">
|
||||
Status
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'PENDING')">Pending</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'REVIEWED')">Reviewed</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'SHORTLISTED')">Shortlisted</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'ACCEPTED')">Accepted</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'ENROLLED')">Enrolled</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'REJECTED')">Rejected</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger ml-1" (click)="deleteApplication(application)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="applications.length === 0" class="alert alert-info">
|
||||
No applications found for this course.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Courses List (existing content) -->
|
||||
<div *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm">
|
||||
<div *ngIf="courses.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>Course</th>
|
||||
<th>Category</th>
|
||||
<th>Duration</th>
|
||||
<th>Seats</th>
|
||||
<th>Status</th>
|
||||
<th>Applications</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let course of courses">
|
||||
<td>
|
||||
<img *ngIf="course.imageUrl" [src]="getFullImageUrl(course.imageUrl)"
|
||||
alt="{{ course.title }}" class="img-thumbnail"
|
||||
style="width: 60px; height: 60px; object-fit: cover;">
|
||||
<span *ngIf="!course.imageUrl" class="text-muted">No image</span>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ course.title }}</strong>
|
||||
<br>
|
||||
<small class="text-muted">{{ course.instructor }}</small>
|
||||
<br>
|
||||
<small class="text-muted">{{ course.level }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-secondary">{{ course.category }}</span>
|
||||
</td>
|
||||
<td>{{ course.duration }}</td>
|
||||
<td>{{ course.seats }}</td>
|
||||
<td>
|
||||
<span *ngIf="course.isActive" class="badge badge-success">Active</span>
|
||||
<span *ngIf="!course.isActive" class="badge badge-danger">Inactive</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" (click)="viewApplications(course)">
|
||||
View Applications
|
||||
<span class="badge badge-light ml-1">{{ getApplicationCount(course.id) }}</span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm mr-2" (click)="editCourse(course)">
|
||||
<i class="fa fa-edit"></i> Edit
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" (click)="deleteCourse(course)">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="courses.length === 0" class="alert alert-info">
|
||||
No courses available. Please create a new course.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EducationComponent } from './education.component';
|
||||
|
||||
describe('EducationComponent', () => {
|
||||
let component: EducationComponent;
|
||||
let fixture: ComponentFixture<EducationComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ EducationComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EducationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,385 @@
|
||||
// education.component.ts
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { EducationService, Course, CourseApplication } from '../../service/education.service';
|
||||
import { UpcomingEventsService, UpcomingEvent } from '../../service/upcoming-events.service';
|
||||
import { FileUploadService } from '../../service/file-upload.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-education',
|
||||
templateUrl: './education.component.html',
|
||||
styleUrls: ['./education.component.css'],
|
||||
})
|
||||
export class EducationComponent implements OnInit {
|
||||
courses: Course[] = [];
|
||||
applications: CourseApplication[] = [];
|
||||
upcomingEvents: UpcomingEvent[] = [];
|
||||
selectedCourse: Course | null = null;
|
||||
selectedUpcomingEvent: UpcomingEvent | null = null;
|
||||
courseForm: FormGroup;
|
||||
upcomingEventForm: FormGroup;
|
||||
showCourseForm = false;
|
||||
showUpcomingEventForm = false;
|
||||
editing = false;
|
||||
editingUpcomingEvent = false;
|
||||
showApplications = false;
|
||||
showUpcomingEvents = false;
|
||||
|
||||
// Filter for applications
|
||||
selectedCourseForApplications: Course | null = null;
|
||||
|
||||
// Image upload properties
|
||||
selectedImage: File | null = null;
|
||||
imagePreview: string | null = null;
|
||||
isImageUploading = false;
|
||||
uploadError: string | null = null;
|
||||
|
||||
constructor(
|
||||
private educationService: EducationService,
|
||||
private upcomingEventsService: UpcomingEventsService,
|
||||
private fileUploadService: FileUploadService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.courseForm = this.fb.group({
|
||||
title: ['', Validators.required],
|
||||
description: ['', Validators.required],
|
||||
duration: ['', Validators.required],
|
||||
seats: ['', [Validators.required, Validators.min(1)]],
|
||||
category: ['', Validators.required],
|
||||
level: ['', Validators.required],
|
||||
instructor: ['', Validators.required],
|
||||
price: [''],
|
||||
startDate: [''],
|
||||
eligibility: [''],
|
||||
objectives: [''],
|
||||
imageUrl: [''],
|
||||
isActive: [true]
|
||||
});
|
||||
this.upcomingEventForm = this.fb.group({
|
||||
title: ['', Validators.required],
|
||||
description: ['', Validators.required],
|
||||
schedule: ['', Validators.required],
|
||||
eventDate: [''],
|
||||
isActive: [true]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadCourses();
|
||||
this.loadApplications();
|
||||
this.loadUpcomingEvents();
|
||||
}
|
||||
|
||||
loadCourses() {
|
||||
this.educationService.getAllCourses().subscribe(data => {
|
||||
this.courses = data;
|
||||
});
|
||||
}
|
||||
|
||||
loadApplications() {
|
||||
this.educationService.getAllApplications().subscribe(data => {
|
||||
this.applications = data;
|
||||
});
|
||||
}
|
||||
|
||||
getFullImageUrl(imageUrl: string): string {
|
||||
if (!imageUrl) return '';
|
||||
|
||||
// If it's already a full URL, return as-is
|
||||
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
// Otherwise, prepend your API base URL
|
||||
return environment.apiUrl + imageUrl;
|
||||
}
|
||||
loadUpcomingEvents() {
|
||||
this.upcomingEventsService.getAllUpcomingEvents().subscribe(data => {
|
||||
this.upcomingEvents = data;
|
||||
});
|
||||
}
|
||||
|
||||
// Upcoming Events Management
|
||||
showUpcomingEventsModal() {
|
||||
this.showUpcomingEvents = true;
|
||||
}
|
||||
|
||||
hideUpcomingEventsModal() {
|
||||
this.showUpcomingEvents = false;
|
||||
this.hideUpcomingEventForm();
|
||||
}
|
||||
|
||||
showUpcomingEventFormModal() {
|
||||
this.showUpcomingEventForm = true;
|
||||
this.resetUpcomingEventForm();
|
||||
|
||||
setTimeout(() => {
|
||||
this.upcomingEventForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
hideUpcomingEventForm() {
|
||||
this.showUpcomingEventForm = false;
|
||||
this.resetUpcomingEventForm();
|
||||
}
|
||||
|
||||
editUpcomingEvent(event: UpcomingEvent) {
|
||||
this.selectedUpcomingEvent = event;
|
||||
this.upcomingEventForm.patchValue({
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
schedule: event.schedule,
|
||||
eventDate: event.eventDate || '',
|
||||
isActive: event.isActive
|
||||
});
|
||||
this.editingUpcomingEvent = true;
|
||||
this.showUpcomingEventForm = true;
|
||||
}
|
||||
|
||||
saveUpcomingEvent() {
|
||||
if (this.upcomingEventForm.valid) {
|
||||
const eventData = this.upcomingEventForm.value;
|
||||
eventData.isActive = this.upcomingEventForm.get('isActive')?.value === true;
|
||||
|
||||
if (this.editingUpcomingEvent && this.selectedUpcomingEvent) {
|
||||
this.upcomingEventsService.updateUpcomingEvent(this.selectedUpcomingEvent.id!, eventData).subscribe(() => {
|
||||
this.loadUpcomingEvents();
|
||||
this.hideUpcomingEventForm();
|
||||
});
|
||||
} else {
|
||||
this.upcomingEventsService.createUpcomingEvent(eventData).subscribe(() => {
|
||||
this.loadUpcomingEvents();
|
||||
this.hideUpcomingEventForm();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteUpcomingEvent(event: UpcomingEvent) {
|
||||
if (confirm('Are you sure you want to delete this upcoming event?')) {
|
||||
this.upcomingEventsService.deleteUpcomingEvent(event.id!).subscribe(() => {
|
||||
this.loadUpcomingEvents();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetUpcomingEventForm() {
|
||||
this.upcomingEventForm.reset();
|
||||
this.selectedUpcomingEvent = null;
|
||||
this.editingUpcomingEvent = false;
|
||||
this.upcomingEventForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}
|
||||
|
||||
showCourseFormModal() {
|
||||
this.showCourseForm = true;
|
||||
this.resetCourseForm();
|
||||
|
||||
setTimeout(() => {
|
||||
this.courseForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
hideCourseForm() {
|
||||
this.showCourseForm = false;
|
||||
this.resetCourseForm();
|
||||
}
|
||||
|
||||
editCourse(course: Course) {
|
||||
this.selectedCourse = course;
|
||||
this.courseForm.patchValue({
|
||||
title: course.title,
|
||||
description: course.description,
|
||||
duration: course.duration,
|
||||
seats: course.seats,
|
||||
category: course.category,
|
||||
level: course.level,
|
||||
instructor: course.instructor,
|
||||
price: course.price || '',
|
||||
startDate: course.startDate || '',
|
||||
eligibility: course.eligibility ? course.eligibility.join(', ') : '',
|
||||
objectives: course.objectives ? course.objectives.join(', ') : '',
|
||||
imageUrl: course.imageUrl || '',
|
||||
isActive: course.isActive
|
||||
});
|
||||
|
||||
if (course.imageUrl) {
|
||||
this.imagePreview = course.imageUrl;
|
||||
}
|
||||
|
||||
this.editing = true;
|
||||
this.showCourseForm = true;
|
||||
}
|
||||
|
||||
// Image handling methods
|
||||
onImageSelected(event: any) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
this.selectedImage = file;
|
||||
this.uploadError = null;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: any) => {
|
||||
this.imagePreview = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
uploadImage(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.selectedImage) {
|
||||
resolve('');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isImageUploading = true;
|
||||
this.uploadError = null;
|
||||
|
||||
this.fileUploadService.uploadFile(this.selectedImage).subscribe({
|
||||
next: (response) => {
|
||||
this.isImageUploading = false;
|
||||
this.courseForm.patchValue({ imageUrl: response.url });
|
||||
resolve(response.url);
|
||||
},
|
||||
error: (error) => {
|
||||
this.isImageUploading = false;
|
||||
this.uploadError = error.error?.error || 'Failed to upload image';
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeImage() {
|
||||
this.selectedImage = null;
|
||||
this.imagePreview = null;
|
||||
this.courseForm.patchValue({ imageUrl: '' });
|
||||
this.uploadError = null;
|
||||
|
||||
const fileInput = document.getElementById('imageInput') as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async saveCourse() {
|
||||
if (this.courseForm.valid) {
|
||||
try {
|
||||
if (this.selectedImage) {
|
||||
await this.uploadImage();
|
||||
}
|
||||
|
||||
const courseData = this.courseForm.value;
|
||||
|
||||
courseData.isActive = this.courseForm.get('isActive')?.value === true;
|
||||
|
||||
courseData.eligibility = courseData.eligibility
|
||||
? courseData.eligibility.split(',').map((item: string) => item.trim()).filter((item: string) => item.length > 0)
|
||||
: [];
|
||||
courseData.objectives = courseData.objectives
|
||||
? courseData.objectives.split(',').map((item: string) => item.trim()).filter((item: string) => item.length > 0)
|
||||
: [];
|
||||
|
||||
if (this.editing && this.selectedCourse) {
|
||||
this.educationService.updateCourse(this.selectedCourse.id!, courseData).subscribe(() => {
|
||||
this.loadCourses();
|
||||
this.hideCourseForm();
|
||||
});
|
||||
} else {
|
||||
this.educationService.createCourse(courseData).subscribe(() => {
|
||||
this.loadCourses();
|
||||
this.hideCourseForm();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving course:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteCourse(course: Course) {
|
||||
if (confirm('Are you sure you want to delete this course?')) {
|
||||
this.educationService.deleteCourse(course.id!).subscribe(() => {
|
||||
this.loadCourses();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetCourseForm() {
|
||||
this.courseForm.reset();
|
||||
this.selectedCourse = null;
|
||||
this.editing = false;
|
||||
this.selectedImage = null;
|
||||
this.imagePreview = null;
|
||||
this.uploadError = null;
|
||||
this.courseForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
|
||||
const fileInput = document.getElementById('imageInput') as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Application management
|
||||
viewApplications(course: Course) {
|
||||
this.selectedCourseForApplications = course;
|
||||
this.showApplications = true;
|
||||
this.educationService.getApplicationsByCourseId(course.id!).subscribe(data => {
|
||||
this.applications = data;
|
||||
});
|
||||
}
|
||||
|
||||
hideApplications() {
|
||||
this.showApplications = false;
|
||||
this.selectedCourseForApplications = null;
|
||||
this.loadApplications();
|
||||
}
|
||||
|
||||
updateApplicationStatus(application: CourseApplication, status: string) {
|
||||
this.educationService.updateApplicationStatus(application.id!, status).subscribe(() => {
|
||||
if (this.selectedCourseForApplications) {
|
||||
this.viewApplications(this.selectedCourseForApplications);
|
||||
} else {
|
||||
this.loadApplications();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteApplication(application: CourseApplication) {
|
||||
if (confirm('Are you sure you want to delete this application?')) {
|
||||
this.educationService.deleteApplication(application.id!).subscribe(() => {
|
||||
if (this.selectedCourseForApplications) {
|
||||
this.viewApplications(this.selectedCourseForApplications);
|
||||
} else {
|
||||
this.loadApplications();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatusBadgeClass(status: string): string {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'pending': return 'badge-warning';
|
||||
case 'reviewed': return 'badge-info';
|
||||
case 'shortlisted': return 'badge-primary';
|
||||
case 'accepted': return 'badge-success';
|
||||
case 'enrolled': return 'badge-success';
|
||||
case 'rejected': return 'badge-danger';
|
||||
default: return 'badge-light';
|
||||
}
|
||||
}
|
||||
|
||||
getApplicationCount(courseId?: number): number {
|
||||
if (!courseId) return 0;
|
||||
return this.applications.filter(app => app.course?.id === courseId).length;
|
||||
}}
|
||||
|
||||
@ -24,6 +24,16 @@
|
||||
<label for="subTitle" class="form-label text-primary">Subtitle</label>
|
||||
<input id="subTitle" formControlName="subTitle" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label for="description" class="form-label text-primary">Description</label>
|
||||
<textarea id="description" formControlName="description" class="form-control" rows="3"
|
||||
placeholder="Brief description for the event card"></textarea>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label for="detail" class="form-label text-primary">Detail</label>
|
||||
<textarea id="detail" formControlName="detail" class="form-control" rows="3"
|
||||
placeholder="Detailed information about the event"></textarea>
|
||||
</div>
|
||||
<div class="form-group mb-3">
|
||||
<label for="date" class="form-label text-primary">Date</label>
|
||||
<input id="date" formControlName="date" class="form-control" />
|
||||
@ -43,6 +53,119 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<!-- Main Image Section -->
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label text-primary">Main Image</label>
|
||||
|
||||
<!-- Image Upload Section -->
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Upload Image File</h6>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<input type="file"
|
||||
class="form-control"
|
||||
accept="image/*"
|
||||
(change)="onMainImageFileSelected($event)"
|
||||
#mainImageFile>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button type="button"
|
||||
class="btn btn-primary btn-sm w-100"
|
||||
(click)="uploadMainImage()"
|
||||
[disabled]="!selectedMainImageFile || mainImageUploading">
|
||||
<span *ngIf="mainImageUploading" class="spinner-border spinner-border-sm me-1"></span>
|
||||
{{ mainImageUploading ? 'Uploading...' : 'Upload' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- URL Input Section -->
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Or Enter Image URL</h6>
|
||||
<input id="mainImage"
|
||||
formControlName="mainImage"
|
||||
class="form-control"
|
||||
placeholder="https://images.unsplash.com/..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Preview -->
|
||||
<div *ngIf="eventForm.get('mainImage')?.value" class="mt-2">
|
||||
<img [src]="eventForm.get('mainImage')?.value"
|
||||
alt="Main Image Preview"
|
||||
class="img-thumbnail"
|
||||
style="max-width: 200px; max-height: 150px;">
|
||||
</div>
|
||||
|
||||
<small class="form-text text-muted">This will be the primary image displayed for the event</small>
|
||||
</div>
|
||||
|
||||
<!-- Gallery Images Section -->
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label text-primary">Gallery Images</label>
|
||||
|
||||
<!-- Gallery Upload Section -->
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Upload Gallery Images</h6>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<input type="file"
|
||||
class="form-control"
|
||||
accept="image/*"
|
||||
multiple
|
||||
(change)="onGalleryImagesSelected($event)"
|
||||
#galleryImageFiles>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button type="button"
|
||||
class="btn btn-primary btn-sm w-100"
|
||||
(click)="uploadGalleryImages()"
|
||||
[disabled]="!selectedGalleryImageFiles?.length || galleryImagesUploading">
|
||||
<span *ngIf="galleryImagesUploading" class="spinner-border spinner-border-sm me-1"></span>
|
||||
{{ galleryImagesUploading ? 'Uploading...' : 'Upload' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manual Gallery URL Entry -->
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Or Add Gallery URLs</h6>
|
||||
<div formArrayName="galleryImages">
|
||||
<div *ngFor="let image of galleryImages.controls; let i = index" class="row mb-2">
|
||||
<div class="col-8">
|
||||
<input [formControlName]="i" class="form-control" placeholder="https://images.unsplash.com/..." />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button type="button" class="btn btn-danger btn-sm w-100" (click)="removeGalleryImage(i)">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
<!-- Image Preview -->
|
||||
<div class="col-12" *ngIf="galleryImages.at(i).value">
|
||||
<img [src]="galleryImages.at(i).value"
|
||||
alt="Gallery Preview"
|
||||
class="img-thumbnail mt-2"
|
||||
style="max-width: 100px; max-height: 80px;">
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" (click)="addGalleryImage()">
|
||||
Add Gallery URL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<small class="form-text text-muted">Add up to 4 gallery images for the event grid</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label text-primary">Venues</label>
|
||||
<div formArrayName="venues">
|
||||
@ -69,7 +192,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger btn-sm float-end" (click)="removeVenue(i)">
|
||||
<!-- <i class="bi bi-trash"></i> -->X
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -86,7 +209,7 @@
|
||||
<div class="card-body">
|
||||
<input [formControlName]="i" class="form-control" placeholder="Highlight" />
|
||||
<button type="button" class="btn btn-danger btn-sm float-end mt-2" (click)="removeHighlight(i)">
|
||||
<!-- <i class="bi bi-trash"></i> -->X
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -103,7 +226,7 @@
|
||||
<div class="card-body">
|
||||
<input [formControlName]="i" class="form-control" placeholder="Organiser" />
|
||||
<button type="button" class="btn btn-danger btn-sm float-end mt-2" (click)="removeOrganiser(i)">
|
||||
<!-- <i class="bi bi-trash"></i> -->X
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -121,7 +244,7 @@
|
||||
<div [formGroupName]="i">
|
||||
<div class="row mb-2">
|
||||
<div class="col-8">
|
||||
<input formControlName="desc" placeholder="Description" class="form-control" />
|
||||
<input formControlName="description" placeholder="Description" class="form-control" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input formControlName="cost" placeholder="Cost" type="number" class="form-control" />
|
||||
@ -129,7 +252,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger btn-sm float-end mt-2" (click)="removeFee(i)">
|
||||
<!-- <i class="bi bi-trash"></i> -->X
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -142,4 +265,4 @@
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
@ -11,125 +10,334 @@ import { EventService } from 'src/app/service/event.service';
|
||||
})
|
||||
export class EventFormComponent implements OnInit {
|
||||
|
||||
eventForm: FormGroup;
|
||||
eventId: number | null;
|
||||
|
||||
// File upload properties
|
||||
selectedMainImageFile: File | null = null;
|
||||
selectedGalleryImageFiles: File[] = [];
|
||||
mainImageUploading = false;
|
||||
galleryImagesUploading = false;
|
||||
|
||||
|
||||
eventForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private eventService: EventService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
|
||||
) {}
|
||||
eventId: number | null;
|
||||
|
||||
ngOnInit() {
|
||||
this.eventForm = this.fb.group({
|
||||
code: ['', Validators.required],
|
||||
year: ['', Validators.required],
|
||||
subject: ['', Validators.required],
|
||||
title: ['', Validators.required],
|
||||
subTitle: [''],
|
||||
date: ['', Validators.required],
|
||||
venues: this.fb.array([]),
|
||||
highlights: this.fb.array([]),
|
||||
organisers: this.fb.array([]),
|
||||
fees: this.fb.array([]),
|
||||
phone: ['', Validators.required],
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
isActive: [true]
|
||||
});
|
||||
|
||||
this.route.paramMap.subscribe(params => {
|
||||
const idParam = params.get('id');
|
||||
this.eventId = idParam ? +idParam : null;
|
||||
|
||||
if (this.eventId !== null) {
|
||||
this.loadEvent(this.eventId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadEvent(id: number): void {
|
||||
this.eventService.getEvent(id).subscribe(event => {
|
||||
this.eventForm.patchValue(event);
|
||||
this.setArrayValues('venues', event.venues);
|
||||
this.setArrayValues('highlights', event.highlights);
|
||||
this.setArrayValues('organisers', event.organisers);
|
||||
this.setArrayValues('fees', event.fees);
|
||||
});
|
||||
}
|
||||
|
||||
setArrayValues(controlName: string, values: any[]): void {
|
||||
const array = this.eventForm.get(controlName) as FormArray;
|
||||
values.forEach(value => array.push(this.fb.group(value)));
|
||||
}
|
||||
|
||||
get venues(): FormArray {
|
||||
return this.eventForm.get('venues') as FormArray;
|
||||
}
|
||||
|
||||
get highlights(): FormArray {
|
||||
return this.eventForm.get('highlights') as FormArray;
|
||||
}
|
||||
|
||||
get organisers(): FormArray {
|
||||
return this.eventForm.get('organisers') as FormArray;
|
||||
}
|
||||
|
||||
get fees(): FormArray {
|
||||
return this.eventForm.get('fees') as FormArray;
|
||||
}
|
||||
|
||||
addVenue() {
|
||||
this.venues.push(this.fb.group({
|
||||
title: [''],
|
||||
date: [''],
|
||||
address: [''],
|
||||
info: ['']
|
||||
}));
|
||||
}
|
||||
|
||||
removeVenue(index: number) {
|
||||
this.venues.removeAt(index);
|
||||
}
|
||||
|
||||
addHighlight() {
|
||||
this.highlights.push(this.fb.control(''));
|
||||
}
|
||||
|
||||
removeHighlight(index: number) {
|
||||
this.highlights.removeAt(index);
|
||||
}
|
||||
|
||||
addOrganiser() {
|
||||
this.organisers.push(this.fb.control(''));
|
||||
}
|
||||
|
||||
removeOrganiser(index: number) {
|
||||
this.organisers.removeAt(index);
|
||||
}
|
||||
|
||||
addFee() {
|
||||
this.fees.push(this.fb.group({
|
||||
desc: [''],
|
||||
cost: ['']
|
||||
}));
|
||||
}
|
||||
|
||||
removeFee(index: number) {
|
||||
this.fees.removeAt(index);
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.eventForm.valid) {
|
||||
if (this.eventId) {
|
||||
this.eventService.updateEvent(this.eventId, this.eventForm.value).subscribe(() => this.router.navigate(['/events']));
|
||||
} else {
|
||||
this.eventService.createEvent(this.eventForm.value).subscribe(() => this.router.navigate(['/events']));
|
||||
}
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private eventService: EventService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.eventForm = this.fb.group({
|
||||
code: ['', Validators.required],
|
||||
year: ['', Validators.required],
|
||||
subject: ['', Validators.required],
|
||||
title: ['', Validators.required],
|
||||
subTitle: [''],
|
||||
description: ['', Validators.required],
|
||||
detail: ['', Validators.required],
|
||||
date: ['', Validators.required],
|
||||
mainImage: [''],
|
||||
galleryImages: this.fb.array([]),
|
||||
venues: this.fb.array([]), // Form uses 'venues' (plural)
|
||||
highlights: this.fb.array([]),
|
||||
organisers: this.fb.array([]),
|
||||
fees: this.fb.array([]), // Form uses 'fees' (plural)
|
||||
phone: ['', Validators.required],
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
isActive: [true]
|
||||
});
|
||||
|
||||
this.route.paramMap.subscribe(params => {
|
||||
const idParam = params.get('id');
|
||||
this.eventId = idParam ? +idParam : null;
|
||||
|
||||
if (this.eventId !== null) {
|
||||
this.loadEvent(this.eventId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// File selection methods
|
||||
onMainImageFileSelected(event: any): void {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
this.selectedMainImageFile = file;
|
||||
console.log('Main image file selected:', file.name);
|
||||
}
|
||||
}
|
||||
|
||||
onGalleryImagesSelected(event: any): void {
|
||||
const files = Array.from(event.target.files) as File[];
|
||||
if (files.length > 0) {
|
||||
this.selectedGalleryImageFiles = files;
|
||||
console.log('Gallery image files selected:', files.map(f => f.name));
|
||||
}
|
||||
}
|
||||
|
||||
// Upload methods
|
||||
uploadMainImage(): void {
|
||||
if (!this.selectedMainImageFile) return;
|
||||
|
||||
this.mainImageUploading = true;
|
||||
this.eventService.uploadImage(this.selectedMainImageFile).subscribe({
|
||||
next: (response) => {
|
||||
console.log('Main image uploaded:', response);
|
||||
this.eventForm.patchValue({ mainImage: response.url });
|
||||
this.selectedMainImageFile = null;
|
||||
this.mainImageUploading = false;
|
||||
|
||||
// Clear the file input
|
||||
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error uploading main image:', error);
|
||||
this.mainImageUploading = false;
|
||||
alert('Error uploading image. Please try again.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uploadGalleryImages(): void {
|
||||
if (!this.selectedGalleryImageFiles || this.selectedGalleryImageFiles.length === 0) return;
|
||||
|
||||
this.galleryImagesUploading = true;
|
||||
this.eventService.uploadMultipleImages(this.selectedGalleryImageFiles).subscribe({
|
||||
next: (responses) => {
|
||||
console.log('Gallery images uploaded:', responses);
|
||||
|
||||
// Add uploaded image URLs to the form array
|
||||
const galleryArray = this.galleryImages;
|
||||
responses.forEach((response: any) => {
|
||||
if (response && response.url) {
|
||||
galleryArray.push(this.fb.control(response.url));
|
||||
}
|
||||
});
|
||||
|
||||
this.selectedGalleryImageFiles = [];
|
||||
this.galleryImagesUploading = false;
|
||||
|
||||
// Clear the file input
|
||||
const fileInputs = document.querySelectorAll('input[type="file"]');
|
||||
fileInputs.forEach((input: any) => {
|
||||
if (input.multiple) input.value = '';
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error uploading gallery images:', error);
|
||||
this.galleryImagesUploading = false;
|
||||
alert('Error uploading gallery images. Please try again.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadEvent(id: number): void {
|
||||
this.eventService.getEvent(id).subscribe(event => {
|
||||
if (event) {
|
||||
this.eventForm.patchValue({
|
||||
code: event.code,
|
||||
year: event.year,
|
||||
subject: event.subject,
|
||||
title: event.title,
|
||||
subTitle: event.subTitle,
|
||||
description: event.description,
|
||||
detail: event.detail,
|
||||
date: event.date,
|
||||
mainImage: event.mainImage,
|
||||
phone: event.phone,
|
||||
email: event.email,
|
||||
isActive: event.isActive
|
||||
});
|
||||
|
||||
// Map backend 'venue' to form 'venues'
|
||||
this.setArrayValues('venues', event.venue || []);
|
||||
|
||||
// Map backend 'fee' to form 'fees'
|
||||
this.setArrayValues('fees', event.fee || []);
|
||||
|
||||
this.setArrayValues('highlights', event.highlights || []);
|
||||
this.setArrayValues('organisers', event.organisers || []);
|
||||
this.setStringArrayValues('galleryImages', event.galleryImages || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setArrayValues(controlName: string, values: any[]): void {
|
||||
const array = this.eventForm.get(controlName) as FormArray;
|
||||
array.clear();
|
||||
|
||||
if (values && values.length > 0) {
|
||||
values.forEach(value => {
|
||||
if (controlName === 'venues') {
|
||||
array.push(this.fb.group({
|
||||
title: [value.title || ''],
|
||||
date: [value.date || ''],
|
||||
address: [value.address || ''],
|
||||
info: [value.info || '']
|
||||
}));
|
||||
} else if (controlName === 'fees') {
|
||||
array.push(this.fb.group({
|
||||
description: [value.description || ''], // Changed from 'desc' to 'description'
|
||||
cost: [value.cost || '']
|
||||
}));
|
||||
} else {
|
||||
array.push(this.fb.control(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setStringArrayValues(controlName: string, values: string[]): void {
|
||||
const array = this.eventForm.get(controlName) as FormArray;
|
||||
array.clear();
|
||||
if (values && values.length > 0) {
|
||||
values.forEach(value => array.push(this.fb.control(value)));
|
||||
}
|
||||
}
|
||||
|
||||
get venues(): FormArray {
|
||||
return this.eventForm.get('venues') as FormArray;
|
||||
}
|
||||
|
||||
get highlights(): FormArray {
|
||||
return this.eventForm.get('highlights') as FormArray;
|
||||
}
|
||||
|
||||
get organisers(): FormArray {
|
||||
return this.eventForm.get('organisers') as FormArray;
|
||||
}
|
||||
|
||||
get fees(): FormArray {
|
||||
return this.eventForm.get('fees') as FormArray;
|
||||
}
|
||||
|
||||
get galleryImages(): FormArray {
|
||||
return this.eventForm.get('galleryImages') as FormArray;
|
||||
}
|
||||
|
||||
addGalleryImage() {
|
||||
this.galleryImages.push(this.fb.control(''));
|
||||
}
|
||||
|
||||
removeGalleryImage(index: number) {
|
||||
this.galleryImages.removeAt(index);
|
||||
}
|
||||
|
||||
addVenue() {
|
||||
this.venues.push(this.fb.group({
|
||||
title: [''],
|
||||
date: [''],
|
||||
address: [''],
|
||||
info: ['']
|
||||
}));
|
||||
}
|
||||
|
||||
removeVenue(index: number) {
|
||||
this.venues.removeAt(index);
|
||||
}
|
||||
|
||||
addHighlight() {
|
||||
this.highlights.push(this.fb.control(''));
|
||||
}
|
||||
|
||||
removeHighlight(index: number) {
|
||||
this.highlights.removeAt(index);
|
||||
}
|
||||
|
||||
addOrganiser() {
|
||||
this.organisers.push(this.fb.control(''));
|
||||
}
|
||||
|
||||
removeOrganiser(index: number) {
|
||||
this.organisers.removeAt(index);
|
||||
}
|
||||
|
||||
addFee() {
|
||||
this.fees.push(this.fb.group({
|
||||
description: [''], // Changed from 'desc' to 'description'
|
||||
cost: ['']
|
||||
}));
|
||||
}
|
||||
|
||||
removeFee(index: number) {
|
||||
this.fees.removeAt(index);
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.eventForm.valid) {
|
||||
const formValue = this.eventForm.value;
|
||||
|
||||
// Transform form data to match backend structure
|
||||
const eventData = {
|
||||
...formValue,
|
||||
venue: formValue.venues, // Map 'venues' to 'venue' for backend
|
||||
fee: formValue.fees, // Map 'fees' to 'fee' for backend
|
||||
galleryImages: formValue.galleryImages?.filter((img: string) => img?.trim() !== '') || [],
|
||||
isDeleted: false // Add default value for isDeleted
|
||||
};
|
||||
|
||||
// Remove the plural properties that don't exist in backend
|
||||
delete eventData.venues;
|
||||
delete eventData.fees;
|
||||
|
||||
console.log('Submitting event data:', eventData);
|
||||
|
||||
if (this.eventId) {
|
||||
this.eventService.updateEvent(this.eventId, eventData).subscribe({
|
||||
next: (response) => {
|
||||
console.log('Event updated successfully:', response);
|
||||
this.router.navigate(['/events']);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error updating event:', error);
|
||||
alert('Error updating event. Please check the console for details.');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.eventService.createEvent(eventData).subscribe({
|
||||
next: (response) => {
|
||||
console.log('Event created successfully:', response);
|
||||
this.router.navigate(['/events']);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error creating event:', error);
|
||||
alert('Error creating event. Please check the console for details.');
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('Form is invalid');
|
||||
console.log('Form errors:', this.getFormValidationErrors());
|
||||
|
||||
// Mark all fields as touched to show validation errors
|
||||
Object.keys(this.eventForm.controls).forEach(key => {
|
||||
const control = this.eventForm.get(key);
|
||||
if (control) {
|
||||
control.markAsTouched();
|
||||
if (control instanceof FormArray) {
|
||||
control.controls.forEach(arrayControl => {
|
||||
arrayControl.markAsTouched();
|
||||
if (arrayControl instanceof FormGroup) {
|
||||
Object.keys(arrayControl.controls).forEach(innerKey => {
|
||||
arrayControl.get(innerKey)?.markAsTouched();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to debug form validation errors
|
||||
getFormValidationErrors() {
|
||||
const formErrors: any = {};
|
||||
Object.keys(this.eventForm.controls).forEach(key => {
|
||||
const controlErrors = this.eventForm.get(key)?.errors;
|
||||
if (controlErrors) {
|
||||
formErrors[key] = controlErrors;
|
||||
}
|
||||
});
|
||||
return formErrors;
|
||||
}
|
||||
}
|
||||
@ -7,41 +7,61 @@
|
||||
<i class="bi bi-plus"></i> Add New Event
|
||||
</a>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Year</th>
|
||||
<th>Subject</th>
|
||||
<th>Title</th>
|
||||
<th>Subtitle</th>
|
||||
<th>Date</th>
|
||||
<th>Phone</th>
|
||||
<th>Email</th>
|
||||
<th>Active</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let event of events">
|
||||
<td>{{ event.code }}</td>
|
||||
<td>{{ event.year }}</td>
|
||||
<td>{{ event.subject }}</td>
|
||||
<td>{{ event.title }}</td>
|
||||
<td>{{ event.subTitle }}</td>
|
||||
<td>{{ event.date }}</td>
|
||||
<td>{{ event.phone }}</td>
|
||||
<td>{{ event.email }}</td>
|
||||
<td>{{ event.isActive ? 'Yes' : 'No' }}</td>
|
||||
<td>
|
||||
<a [routerLink]="['/eventForm', event.id]" class="btn btn-warning btn-sm">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</a>
|
||||
<button (click)="deleteEvent(event.id)" class="btn btn-danger btn-sm">
|
||||
<i class="bi bi-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Year</th>
|
||||
<th>Subject</th>
|
||||
<th>Title</th>
|
||||
<th>Description</th>
|
||||
<th>Date</th>
|
||||
<th>Fees</th>
|
||||
<th>Main Image</th>
|
||||
<th>Active</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let event of events">
|
||||
<td>{{ event.code }}</td>
|
||||
<td>{{ event.year }}</td>
|
||||
<td>{{ event.subject }}</td>
|
||||
<td>{{ event.title }}</td>
|
||||
<td>{{ event.description?.substring(0, 50) }}{{ event.description?.length > 50 ? '...' : '' }}</td>
|
||||
<td>{{ event.date }}</td>
|
||||
<td>
|
||||
<span *ngIf="event.fee && event.fee.length > 0">
|
||||
{{ event.fee[0].description }}: ₹{{ event.fee[0].cost }}
|
||||
<span *ngIf="event.fee.length > 1"> (+{{ event.fee.length - 1 }} more)</span>
|
||||
</span>
|
||||
<span *ngIf="!event.fee || event.fee.length === 0" class="text-muted">No fees</span>
|
||||
</td>
|
||||
<td>
|
||||
<img *ngIf="event.mainImage"
|
||||
[src]="event.mainImage"
|
||||
alt="Event Image"
|
||||
style="width: 50px; height: 30px; object-fit: cover; border-radius: 4px;"
|
||||
onerror="this.style.display='none'">
|
||||
<span *ngIf="!event.mainImage" class="text-muted">No image</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge"
|
||||
[ngClass]="event.isActive ? 'bg-success' : 'bg-secondary'">
|
||||
{{ event.isActive ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/dashboard/eventForm', event.id]" class="btn btn-warning btn-sm me-2">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</a>
|
||||
<button (click)="deleteEvent(event.id)" class="btn btn-danger btn-sm">
|
||||
<i class="bi bi-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,52 +1,67 @@
|
||||
<div class="container">
|
||||
<div class="row mb-2 mt-2 text-center">
|
||||
<div class="col-md-4"></div>
|
||||
<div class="col-md-4">
|
||||
<h5>Admin Dashboard</h5>
|
||||
</div>
|
||||
<div class="col-md-4"></div>
|
||||
<div class="row mb-2 mt-2 text-center">
|
||||
<div class="col-md-4"></div>
|
||||
<div class="col-md-4">
|
||||
<h5>Admin Dashboard</h5>
|
||||
</div>
|
||||
<div class="col-md-4"></div>
|
||||
</div>
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-md breadcrumb">
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<div class="nav nav-pills">
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/home" routerLinkActive="active" (click)="changeTitle('Home')">
|
||||
<i class="fa fa-home"></i>
|
||||
Home
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/userManagement" routerLinkActive="active" (click)="changeTitle('Users')">
|
||||
<i class="fa fa-users"></i>
|
||||
Users
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/professorManagement" routerLinkActive="active" (click)="changeTitle('Professors')">
|
||||
<i class="fa fa-chalkboard-teacher"></i>
|
||||
Professors
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/blogs" routerLinkActive="active" (click)="changeTitle('Professors')">
|
||||
<i class="fa fa-chalkboard-teacher"></i>
|
||||
Blogs
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/events" routerLinkActive="active" (click)="changeTitle('Professors')">
|
||||
<i class="fa fa-chalkboard-teacher"></i>
|
||||
Events
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-3" routerLink="/dashboard/settings" routerLinkActive="active" (click)="changeTitle('Settings')">
|
||||
<i class="fa fa-cogs"></i>
|
||||
Settings
|
||||
</a>
|
||||
<a class="nav-item nav-link move-right mr-3" routerLink="/dashboard/profile" routerLinkActive="active" (click)="changeTitle('Profile')">
|
||||
Welcome, {{ loggedInUser.firstName }} {{ loggedInUser.lastName }}
|
||||
<i class="fa fa-user"></i>
|
||||
</a>
|
||||
<!-- <a class="nav-item nav-link ml-1" (click)="logout()">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-md breadcrumb">
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<div class="nav nav-pills">
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/home" routerLinkActive="active"
|
||||
(click)="changeTitle('Home')">
|
||||
<i class="fa fa-home"></i>
|
||||
Home
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/userManagement" routerLinkActive="active"
|
||||
(click)="changeTitle('Users')">
|
||||
<i class="fa fa-users"></i>
|
||||
Users
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/professorManagement" routerLinkActive="active"
|
||||
(click)="changeTitle('Professors')">
|
||||
<i class="fa fa-chalkboard-teacher"></i>
|
||||
Professors
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/blogs" routerLinkActive="active"
|
||||
(click)="changeTitle('Blogs')">
|
||||
<i class="fa fa-blog"></i>
|
||||
Blogs
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/education" routerLinkActive="active"
|
||||
(click)="changeTitle('Education')">
|
||||
<i class="fa fa-graduation-cap"></i>
|
||||
Education
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/events" routerLinkActive="active"
|
||||
(click)="changeTitle('Events')">
|
||||
<i class="fa fa-calendar"></i>
|
||||
Events
|
||||
</a>
|
||||
<a class="nav-item nav-link ml-1" routerLink="/dashboard/career" routerLinkActive="active"
|
||||
(click)="changeTitle('Career')">
|
||||
<i class="fa fa-briefcase"></i>
|
||||
Careers
|
||||
</a>
|
||||
|
||||
<a class="nav-item nav-link ml-3" routerLink="/dashboard/settings" routerLinkActive="active"
|
||||
(click)="changeTitle('Settings')">
|
||||
<i class="fa fa-cogs"></i>
|
||||
Settings
|
||||
</a>
|
||||
<a class="nav-item nav-link move-right mr-3" routerLink="/dashboard/profile" routerLinkActive="active"
|
||||
(click)="changeTitle('Profile')">
|
||||
Welcome, {{ loggedInUser.firstName }} {{ loggedInUser.lastName }}
|
||||
<i class="fa fa-user"></i>
|
||||
</a>
|
||||
<!-- <a class="nav-item nav-link ml-1" (click)="logout()">
|
||||
<i class="fa fa-sign-out-alt"></i>
|
||||
Logout
|
||||
</a> -->
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
@ -39,7 +39,7 @@
|
||||
data-bs-toggle="tab"
|
||||
href="#profile"
|
||||
>
|
||||
Welcome, {{ loggedInUser.firstName }} {{ loggedInUser.lastName }}
|
||||
Welcome, {{ loggedInUser?.firstName }} {{ loggedInUser?.lastName }}
|
||||
<i class="fa fa-user"></i>
|
||||
</a>
|
||||
</div>
|
||||
@ -91,6 +91,7 @@
|
||||
<th>First Name</th>
|
||||
<th>Last Name</th>
|
||||
<th>Email</th>
|
||||
<th>Category</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@ -118,6 +119,18 @@
|
||||
<td (click)="onSelectProfessor(professor)">
|
||||
{{ professor?.email }}
|
||||
</td>
|
||||
<td (click)="onSelectProfessor(professor)">
|
||||
<span
|
||||
class="badge"
|
||||
[ngClass]="{
|
||||
'bg-info': professor?.category === 'FACULTY',
|
||||
'bg-secondary': professor?.category === 'SUPPORT_TEAM',
|
||||
'bg-warning': professor?.category === 'TRAINEE_FELLOW'
|
||||
}"
|
||||
>
|
||||
{{ getCategoryDisplayName(professor?.category) }}
|
||||
</span>
|
||||
</td>
|
||||
<td (click)="onSelectProfessor(professor)">
|
||||
<span
|
||||
class="badge"
|
||||
@ -167,6 +180,10 @@
|
||||
data-bs-target="#editProfessorModal"
|
||||
></button>
|
||||
|
||||
<!-- Hidden buttons for modal control -->
|
||||
<button [hidden]="true" type="button" id="new-professor-close" data-bs-dismiss="modal"></button>
|
||||
<button [hidden]="true" type="button" id="closeEditProfessorButton" data-bs-dismiss="modal"></button>
|
||||
|
||||
<!-- Settings -->
|
||||
<div class="tab-pane fade" id="settings">
|
||||
<p>Settings content goes here...</p>
|
||||
@ -188,7 +205,7 @@
|
||||
aria-labelledby="addProfessorModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add New Professor</h5>
|
||||
@ -206,64 +223,303 @@
|
||||
#newProfessorForm="ngForm"
|
||||
(ngSubmit)="onAddNewProfessor(newProfessorForm)"
|
||||
>
|
||||
<!-- Form Fields for Adding New Professor -->
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
name="firstName"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
/>
|
||||
<!-- Basic Information -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
name="firstName"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="email">Email *</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
name="phone"
|
||||
class="form-control"
|
||||
ngModel
|
||||
placeholder="Contact number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="category">Category *</label>
|
||||
<select
|
||||
id="category"
|
||||
name="category"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
>
|
||||
<option value="">Select Category</option>
|
||||
<option value="FACULTY">Faculty</option>
|
||||
<option value="SUPPORT_TEAM">Support Team</option>
|
||||
<option value="TRAINEE_FELLOW">Trainee/Fellow</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="status">Status *</label>
|
||||
<select
|
||||
id="status"
|
||||
name="status"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
>
|
||||
<option [value]="WorkingStatus.ACTIVE">Active</option>
|
||||
<option [value]="WorkingStatus.ON_LEAVE">On Leave</option>
|
||||
<option [value]="WorkingStatus.RETIRED">Retired</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="experience">Experience</label>
|
||||
<input
|
||||
type="text"
|
||||
id="experience"
|
||||
name="experience"
|
||||
class="form-control"
|
||||
placeholder="e.g., 10+ years"
|
||||
ngModel
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status">Status</label>
|
||||
<select
|
||||
id="status"
|
||||
name="status"
|
||||
class="form-control"
|
||||
ngModel
|
||||
required
|
||||
>
|
||||
<option [value]="WorkingStatus.ACTIVE">Active</option>
|
||||
<option [value]="WorkingStatus.ON_LEAVE">On Leave</option>
|
||||
<option [value]="WorkingStatus.RETIRED">Retired</option>
|
||||
</select>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="department">Department</label>
|
||||
<input
|
||||
type="text"
|
||||
id="department"
|
||||
name="department"
|
||||
class="form-control"
|
||||
ngModel
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="position">Position</label>
|
||||
<input
|
||||
type="text"
|
||||
id="position"
|
||||
name="position"
|
||||
class="form-control"
|
||||
ngModel
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="form-group">
|
||||
<label for="profileImageUrl">Profile Image URL</label>
|
||||
<input
|
||||
type="url"
|
||||
id="profileImageUrl"
|
||||
name="profileImageUrl"
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="designation">Designation</label>
|
||||
<input
|
||||
type="text"
|
||||
id="designation"
|
||||
name="designation"
|
||||
class="form-control"
|
||||
ngModel
|
||||
placeholder="Professional title"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="officeLocation">Office Location</label>
|
||||
<input
|
||||
type="text"
|
||||
id="officeLocation"
|
||||
name="officeLocation"
|
||||
class="form-control"
|
||||
ngModel
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="specialty">Specialty</label>
|
||||
<input
|
||||
type="text"
|
||||
id="specialty"
|
||||
name="specialty"
|
||||
class="form-control"
|
||||
ngModel
|
||||
placeholder="Medical specialty or area of expertise"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
class="form-control"
|
||||
rows="3"
|
||||
ngModel
|
||||
/>
|
||||
</div> -->
|
||||
placeholder="Brief professional description"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="certification">Certification</label>
|
||||
<textarea
|
||||
id="certification"
|
||||
name="certification"
|
||||
class="form-control"
|
||||
rows="2"
|
||||
ngModel
|
||||
placeholder="Educational qualifications and certifications"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="training">Training</label>
|
||||
<textarea
|
||||
id="training"
|
||||
name="training"
|
||||
class="form-control"
|
||||
rows="2"
|
||||
ngModel
|
||||
placeholder="Professional training and development"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Work Days Selection -->
|
||||
<div class="form-group">
|
||||
<label>Work Days</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-check form-check-inline" *ngFor="let day of availableDays">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[id]="'day-' + day"
|
||||
[value]="day"
|
||||
[(ngModel)]="selectedWorkDays[day]"
|
||||
name="workDays"
|
||||
/>
|
||||
<label class="form-check-label" [for]="'day-' + day">
|
||||
{{ day }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Awards Section -->
|
||||
<div class="form-group">
|
||||
<label>Awards & Recognition</label>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div *ngFor="let award of newProfessorAwards; let i = index" class="border-bottom pb-2 mb-2">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="award.title"
|
||||
[name]="'awardTitle' + i"
|
||||
placeholder="Award Title"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="award.year"
|
||||
[name]="'awardYear' + i"
|
||||
placeholder="Year"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="2"
|
||||
[(ngModel)]="award.description"
|
||||
[name]="'awardDesc' + i"
|
||||
placeholder="Description"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger btn-sm"
|
||||
(click)="removeAward(i)"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
(click)="addNewAward()"
|
||||
>
|
||||
<i class="fas fa-plus"></i> Add Award
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-prepend">
|
||||
@ -279,8 +535,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
@ -302,7 +556,7 @@
|
||||
aria-labelledby="editProfessorModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit Professor</h5>
|
||||
@ -320,64 +574,295 @@
|
||||
#editProfessorForm="ngForm"
|
||||
(ngSubmit)="onUpdateProfessor(editProfessorForm)"
|
||||
>
|
||||
<!-- Form Fields for Editing Professor -->
|
||||
<div class="form-group">
|
||||
<label for="editFirstName">First Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editFirstName"
|
||||
name="firstName"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.firstName"
|
||||
required
|
||||
/>
|
||||
<!-- Basic Information -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editFirstName">First Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editFirstName"
|
||||
name="firstName"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.firstName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editLastName">Last Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editLastName"
|
||||
name="lastName"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.lastName"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="editLastName">Last Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editLastName"
|
||||
name="lastName"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.lastName"
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editEmail">Email *</label>
|
||||
<input
|
||||
type="email"
|
||||
id="editEmail"
|
||||
name="email"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.email"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editPhone">Phone</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editPhone"
|
||||
name="phone"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.phone"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="editEmail">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="editEmail"
|
||||
name="email"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.email"
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="editCategory">Category *</label>
|
||||
<select
|
||||
id="editCategory"
|
||||
name="category"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.category"
|
||||
required
|
||||
>
|
||||
<option value="FACULTY">Faculty</option>
|
||||
<option value="SUPPORT_TEAM">Support Team</option>
|
||||
<option value="TRAINEE_FELLOW">Trainee/Fellow</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="editStatus">Status *</label>
|
||||
<select
|
||||
id="editStatus"
|
||||
name="status"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.status"
|
||||
required
|
||||
>
|
||||
<option [value]="WorkingStatus.ACTIVE">Active</option>
|
||||
<option [value]="WorkingStatus.ON_LEAVE">On Leave</option>
|
||||
<option [value]="WorkingStatus.RETIRED">Retired</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="editExperience">Experience</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editExperience"
|
||||
name="experience"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.experience"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="editStatus">Status</label>
|
||||
<select
|
||||
id="editStatus"
|
||||
name="status"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.status"
|
||||
required
|
||||
>
|
||||
<option [value]="WorkingStatus.ACTIVE">Active</option>
|
||||
<option [value]="WorkingStatus.ON_LEAVE">On Leave</option>
|
||||
<option [value]="WorkingStatus.RETIRED">Retired</option>
|
||||
</select>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editDepartment">Department</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editDepartment"
|
||||
name="department"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.department"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editPosition">Position</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editPosition"
|
||||
name="position"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.position"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="form-group">
|
||||
<label for="editProfileImageUrl">Profile Image URL</label>
|
||||
<input
|
||||
type="url"
|
||||
id="editProfileImageUrl"
|
||||
name="profileImageUrl"
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editDesignation">Designation</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editDesignation"
|
||||
name="designation"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.designation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editOfficeLocation">Office Location</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editOfficeLocation"
|
||||
name="officeLocation"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.officeLocation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="editSpecialty">Specialty</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editSpecialty"
|
||||
name="specialty"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.specialty"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editDescription">Description</label>
|
||||
<textarea
|
||||
id="editDescription"
|
||||
name="description"
|
||||
class="form-control"
|
||||
[(ngModel)]="selectedProfessor.profileImageUrl"
|
||||
/>
|
||||
</div> -->
|
||||
rows="3"
|
||||
[(ngModel)]="selectedProfessor.description"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editCertification">Certification</label>
|
||||
<textarea
|
||||
id="editCertification"
|
||||
name="certification"
|
||||
class="form-control"
|
||||
rows="2"
|
||||
[(ngModel)]="selectedProfessor.certification"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="editTraining">Training</label>
|
||||
<textarea
|
||||
id="editTraining"
|
||||
name="training"
|
||||
class="form-control"
|
||||
rows="2"
|
||||
[(ngModel)]="selectedProfessor.training"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Work Days Selection for Edit -->
|
||||
<div class="form-group">
|
||||
<label>Work Days</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-check form-check-inline" *ngFor="let day of availableDays">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
[id]="'edit-day-' + day"
|
||||
[value]="day"
|
||||
[(ngModel)]="selectedWorkDays[day]"
|
||||
name="editWorkDays"
|
||||
/>
|
||||
<label class="form-check-label" [for]="'edit-day-' + day">
|
||||
{{ day }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Awards Section for Edit -->
|
||||
<div class="form-group">
|
||||
<label>Awards & Recognition</label>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div *ngFor="let award of selectedProfessorAwards; let i = index" class="border-bottom pb-2 mb-2">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="award.title"
|
||||
[name]="'editAwardTitle' + i"
|
||||
placeholder="Award Title"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
[(ngModel)]="award.year"
|
||||
[name]="'editAwardYear' + i"
|
||||
placeholder="Year"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="2"
|
||||
[(ngModel)]="award.description"
|
||||
[name]="'editAwardDesc' + i"
|
||||
placeholder="Description"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger btn-sm"
|
||||
(click)="removeEditAward(i)"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
(click)="addEditAward()"
|
||||
>
|
||||
<i class="fas fa-plus"></i> Add Award
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-prepend">
|
||||
@ -393,7 +878,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
@ -407,7 +891,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- profile image change form -->
|
||||
<form enctype="multipart/form-data" style="display:none;">
|
||||
<input type="file"
|
||||
@ -427,7 +910,7 @@
|
||||
aria-labelledby=""
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title text-center" id="exampleModalLongTitle">
|
||||
@ -477,10 +960,32 @@
|
||||
<p class="mb-1">
|
||||
{{ selectedProfessor.email || "Email not available" }}
|
||||
</p>
|
||||
<div>
|
||||
Status:
|
||||
<p class="mb-1" *ngIf="selectedProfessor.phone">
|
||||
<strong>Phone:</strong> {{ selectedProfessor.phone }}
|
||||
</p>
|
||||
<p class="mb-1" *ngIf="selectedProfessor.specialty">
|
||||
<strong>Specialty:</strong> {{ selectedProfessor.specialty }}
|
||||
</p>
|
||||
<p class="mb-1" *ngIf="selectedProfessor.experience">
|
||||
<strong>Experience:</strong> {{ selectedProfessor.experience }}
|
||||
</p>
|
||||
<div class="mb-1">
|
||||
<strong>Category:</strong>
|
||||
<span
|
||||
class="badge"
|
||||
class="badge ms-1"
|
||||
[ngClass]="{
|
||||
'bg-info': selectedProfessor.category === 'FACULTY',
|
||||
'bg-secondary': selectedProfessor.category === 'SUPPORT_TEAM',
|
||||
'bg-warning': selectedProfessor.category === 'TRAINEE_FELLOW'
|
||||
}"
|
||||
>
|
||||
{{ getCategoryDisplayName(selectedProfessor.category) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Status:</strong>
|
||||
<span
|
||||
class="badge ms-1"
|
||||
[ngClass]="{
|
||||
'bg-success': selectedProfessor.status === 'ACTIVE',
|
||||
'bg-warning':
|
||||
@ -499,7 +1004,7 @@
|
||||
</div>
|
||||
<div
|
||||
*ngIf="selectedProfessor.joinDate"
|
||||
class="text-muted"
|
||||
class="text-muted mt-1"
|
||||
>
|
||||
<small
|
||||
>Joined
|
||||
@ -523,6 +1028,12 @@
|
||||
selectedProfessor.position || "Not provided"
|
||||
}}</small
|
||||
><br />
|
||||
<small
|
||||
>Designation:
|
||||
{{
|
||||
selectedProfessor.designation || "Not provided"
|
||||
}}</small
|
||||
><br />
|
||||
<small
|
||||
>Office Location:
|
||||
{{
|
||||
@ -533,6 +1044,57 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Information -->
|
||||
<div class="row mt-3" *ngIf="selectedProfessor.description || selectedProfessor.certification || selectedProfessor.training || (selectedProfessor.workDays && selectedProfessor.workDays.length > 0) || (selectedProfessor.awards && selectedProfessor.awards.length > 0)">
|
||||
<div class="col-12">
|
||||
<div *ngIf="selectedProfessor.description">
|
||||
<h6><strong>Description</strong></h6>
|
||||
<p class="text-muted">{{ selectedProfessor.description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6" *ngIf="selectedProfessor.certification">
|
||||
<h6><strong>Certification</strong></h6>
|
||||
<p class="text-muted small">{{ selectedProfessor.certification }}</p>
|
||||
</div>
|
||||
<div class="col-md-6" *ngIf="selectedProfessor.training">
|
||||
<h6><strong>Training</strong></h6>
|
||||
<p class="text-muted small">{{ selectedProfessor.training }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Work Days -->
|
||||
<div *ngIf="selectedProfessor.workDays && selectedProfessor.workDays.length > 0">
|
||||
<h6><strong>Work Days</strong></h6>
|
||||
<div class="d-flex flex-wrap">
|
||||
<span class="badge bg-primary me-1 mb-1" *ngFor="let day of selectedProfessor.workDays">
|
||||
{{ day }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Awards -->
|
||||
<div *ngIf="selectedProfessor.awards && selectedProfessor.awards.length > 0" class="mt-3">
|
||||
<h6><strong>Awards & Recognition</strong></h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-2" *ngFor="let award of selectedProfessor.awards">
|
||||
<div class="card">
|
||||
<div class="card-body p-2">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="card-title mb-1" style="font-size: 0.9rem;">{{ award.title }}</h6>
|
||||
<small class="text-muted">{{ award.year }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-text mt-2" style="font-size: 0.8rem;">{{ award.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
@ -563,4 +1125,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -10,12 +10,19 @@ import { Router } from '@angular/router';
|
||||
import { FileUploadStatus } from 'src/app/model/file-upload.status';
|
||||
import { SubSink } from 'subsink';
|
||||
import { WorkingStatus } from "../../enum/WorkingStatus";
|
||||
import { ProfessorCategory } from "../../enum/professor-category.enum";
|
||||
import { User } from 'src/app/model/user';
|
||||
;
|
||||
import { Role } from 'src/app/enum/role.enum';
|
||||
import { AuthenticationService } from 'src/app/service/authentication.service';
|
||||
import { ProfessorService } from 'src/app/service/professor.service';
|
||||
;
|
||||
|
||||
interface Award {
|
||||
id?: number;
|
||||
title: string;
|
||||
year: string;
|
||||
description: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-professor',
|
||||
@ -23,58 +30,120 @@ import { ProfessorService } from 'src/app/service/professor.service';
|
||||
styleUrls: ['./professor.component.css']
|
||||
})
|
||||
export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
WorkingStatus = WorkingStatus; // Declare enum here
|
||||
WorkingStatus = WorkingStatus;
|
||||
ProfessorCategory = ProfessorCategory;
|
||||
|
||||
private titleSubject = new BehaviorSubject<string>('Professors');
|
||||
public titleAction$ = this.titleSubject.asObservable();
|
||||
public loggedInUser: User;
|
||||
|
||||
|
||||
public professors: Professor[] = [];
|
||||
public loggedInProfessor: Professor;
|
||||
public refreshing: boolean;
|
||||
private subs = new SubSink();
|
||||
|
||||
selectedProfessor: Professor = {
|
||||
professorId: '', // Initialize with empty string or appropriate default
|
||||
firstName: '', // Initialize with empty string or appropriate default
|
||||
lastName: '', // Initialize with empty string or appropriate default
|
||||
email: '', // Initialize with empty string or appropriate default
|
||||
profileImageUrl: '', // Optional property, can be initialized with empty string or `null`
|
||||
status: WorkingStatus.ACTIVE, // Initialize with a default value from your enum
|
||||
department: '', // Initialize with empty string or appropriate default
|
||||
position: '', // Initialize with empty string or appropriate default
|
||||
officeLocation: '', // Initialize with empty string or appropriate default
|
||||
joinDate: new Date() // Initialize with the current date or an appropriate default
|
||||
professorId: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
profileImageUrl: '',
|
||||
status: WorkingStatus.ACTIVE,
|
||||
category: ProfessorCategory.FACULTY,
|
||||
department: '',
|
||||
position: '',
|
||||
officeLocation: '',
|
||||
joinDate: new Date(),
|
||||
phone: '',
|
||||
specialty: '',
|
||||
certification: '',
|
||||
training: '',
|
||||
experience: '',
|
||||
description: '',
|
||||
designation: '',
|
||||
workDays: [],
|
||||
awards: []
|
||||
};
|
||||
public profileImageFileName: string | null;
|
||||
|
||||
public profileImageFileName: string | null;
|
||||
public profileImage: File | null;
|
||||
// public editProfessor: Professor = new Professor();
|
||||
public fileUploadStatus: FileUploadStatus = new FileUploadStatus();
|
||||
|
||||
// Additional properties for extended functionality
|
||||
public availableDays: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
public selectedWorkDays: { [key: string]: boolean } = {};
|
||||
|
||||
// Awards management
|
||||
public newProfessorAwards: Award[] = [];
|
||||
public selectedProfessorAwards: Award[] = [];
|
||||
|
||||
constructor(
|
||||
private professorService: ProfessorService,
|
||||
private notificationService: NotificationService,
|
||||
private router: Router,
|
||||
private authenticationService: AuthenticationService,
|
||||
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.getProfessors(true);
|
||||
this.loggedInUser = this.authenticationService.getUserFromLocalStorage();
|
||||
|
||||
this.initializeAwards();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.unsubscribe();
|
||||
}
|
||||
|
||||
private initializeAwards(): void {
|
||||
this.newProfessorAwards = [];
|
||||
this.selectedProfessorAwards = [];
|
||||
}
|
||||
|
||||
// Award management methods
|
||||
public addNewAward(): void {
|
||||
this.newProfessorAwards.push({
|
||||
title: '',
|
||||
year: '',
|
||||
description: '',
|
||||
imageUrl: ''
|
||||
});
|
||||
}
|
||||
|
||||
public removeAward(index: number): void {
|
||||
this.newProfessorAwards.splice(index, 1);
|
||||
}
|
||||
|
||||
public addEditAward(): void {
|
||||
this.selectedProfessorAwards.push({
|
||||
title: '',
|
||||
year: '',
|
||||
description: '',
|
||||
imageUrl: ''
|
||||
});
|
||||
}
|
||||
|
||||
public removeEditAward(index: number): void {
|
||||
this.selectedProfessorAwards.splice(index, 1);
|
||||
}
|
||||
|
||||
// Category display helper
|
||||
public getCategoryDisplayName(category: string): string {
|
||||
switch (category) {
|
||||
case 'FACULTY':
|
||||
return 'Faculty';
|
||||
case 'SUPPORT_TEAM':
|
||||
return 'Support Team';
|
||||
case 'TRAINEE_FELLOW':
|
||||
return 'Trainee/Fellow';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
handleTitleChange(title: string): void {
|
||||
this.titleSubject.next(title);
|
||||
}
|
||||
|
||||
|
||||
public changeTitle(title: string): void {
|
||||
this.titleSubject.next(title);
|
||||
}
|
||||
@ -99,12 +168,24 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
|
||||
public onSelectProfessor(selectedProfessor: Professor): void {
|
||||
this.selectedProfessor = selectedProfessor;
|
||||
this.selectedProfessorAwards = [...(selectedProfessor.awards || [])];
|
||||
|
||||
// Set up work days for viewing
|
||||
this.selectedWorkDays = {};
|
||||
if (selectedProfessor.workDays && Array.isArray(selectedProfessor.workDays)) {
|
||||
selectedProfessor.workDays.forEach(day => {
|
||||
this.selectedWorkDays[day] = true;
|
||||
});
|
||||
}
|
||||
|
||||
this.clickButton('openProfessorInfo');
|
||||
}
|
||||
|
||||
public onProfileImageChange(fileList: FileList): void {
|
||||
this.profileImageFileName = fileList[0].name;
|
||||
this.profileImage = fileList[0];
|
||||
if (fileList && fileList.length > 0) {
|
||||
this.profileImageFileName = fileList[0].name;
|
||||
this.profileImage = fileList[0];
|
||||
}
|
||||
}
|
||||
|
||||
private sendErrorNotification(message: string): void {
|
||||
@ -116,13 +197,15 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public onAddNewProfessor(professorForm: NgForm): void {
|
||||
const formData = this.professorService.createProfessorFormData(professorForm.value, this.profileImage);
|
||||
const formData = this.createExtendedProfessorFormData(professorForm.value, this.profileImage);
|
||||
this.subs.sink = this.professorService.addProfessor(formData).subscribe(
|
||||
(professor: Professor) => {
|
||||
this.clickButton('new-professor-close');
|
||||
this.getProfessors(false);
|
||||
this.invalidateVariables();
|
||||
professorForm.reset();
|
||||
this.selectedWorkDays = {};
|
||||
this.newProfessorAwards = [];
|
||||
this.notificationService.notify(NotificationType.SUCCESS, `Professor ${professor.firstName} added successfully`);
|
||||
},
|
||||
(errorResponse: HttpErrorResponse) => {
|
||||
@ -134,6 +217,9 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
private invalidateVariables(): void {
|
||||
this.profileImage = null;
|
||||
this.profileImageFileName = null;
|
||||
this.selectedWorkDays = {};
|
||||
this.newProfessorAwards = [];
|
||||
this.selectedProfessorAwards = [];
|
||||
}
|
||||
|
||||
public saveNewProfessor(): void {
|
||||
@ -153,7 +239,10 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
professor.firstName.toLowerCase().includes(searchTerm) ||
|
||||
professor.lastName.toLowerCase().includes(searchTerm) ||
|
||||
professor.email.toLowerCase().includes(searchTerm) ||
|
||||
professor.department.toLowerCase().includes(searchTerm)
|
||||
(professor.department && professor.department.toLowerCase().includes(searchTerm)) ||
|
||||
(professor.phone && professor.phone.toLowerCase().includes(searchTerm)) ||
|
||||
(professor.specialty && professor.specialty.toLowerCase().includes(searchTerm)) ||
|
||||
(professor.category && this.getCategoryDisplayName(professor.category).toLowerCase().includes(searchTerm))
|
||||
) {
|
||||
matchProfessors.push(professor);
|
||||
}
|
||||
@ -166,19 +255,29 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public onEditProfessor(professor: Professor): void {
|
||||
// this.editProfessor = professor;
|
||||
this.selectedProfessor = professor;
|
||||
this.selectedProfessor = { ...professor };
|
||||
|
||||
// Set up work days for editing
|
||||
this.selectedWorkDays = {};
|
||||
if (professor.workDays && Array.isArray(professor.workDays)) {
|
||||
professor.workDays.forEach(day => {
|
||||
this.selectedWorkDays[day] = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Set up awards for editing
|
||||
this.selectedProfessorAwards = [...(professor.awards || [])];
|
||||
|
||||
this.clickButton('openProfessorEdit');
|
||||
}
|
||||
|
||||
onUpdateProfessor(form: NgForm) {
|
||||
public onUpdateProfessor(form: NgForm): void {
|
||||
if (form.invalid) {
|
||||
// Handle form validation errors if needed
|
||||
this.notificationService.notify(NotificationType.ERROR, 'Please fill out all required fields.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = this.professorService.createProfessorFormData(this.selectedProfessor, this.profileImage);
|
||||
const formData = this.createExtendedProfessorFormData(this.selectedProfessor, this.profileImage);
|
||||
|
||||
this.subs.add(this.professorService.updateProfessor(this.selectedProfessor.professorId, formData).subscribe(
|
||||
(professor: Professor) => {
|
||||
@ -193,18 +292,70 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
));
|
||||
}
|
||||
|
||||
private createExtendedProfessorFormData(professor: any, profileImage: File | null): FormData {
|
||||
const formData = new FormData();
|
||||
|
||||
// Basic fields
|
||||
formData.append('firstName', professor.firstName || '');
|
||||
formData.append('lastName', professor.lastName || '');
|
||||
formData.append('email', professor.email || '');
|
||||
formData.append('department', professor.department || '');
|
||||
formData.append('position', professor.position || '');
|
||||
formData.append('officeLocation', professor.officeLocation || '');
|
||||
formData.append('status', professor.status || 'ACTIVE');
|
||||
formData.append('category', professor.category || 'FACULTY');
|
||||
|
||||
// Extended fields
|
||||
formData.append('phone', professor.phone || '');
|
||||
formData.append('specialty', professor.specialty || '');
|
||||
formData.append('experience', professor.experience || '');
|
||||
formData.append('designation', professor.designation || professor.position || '');
|
||||
formData.append('description', professor.description || '');
|
||||
formData.append('certification', professor.certification || '');
|
||||
formData.append('training', professor.training || '');
|
||||
|
||||
// Work days
|
||||
const workDays = Object.keys(this.selectedWorkDays).filter(day => this.selectedWorkDays[day]);
|
||||
if (workDays.length > 0) {
|
||||
workDays.forEach(day => {
|
||||
formData.append('workDays', day);
|
||||
});
|
||||
}
|
||||
|
||||
// Awards - determine which awards array to use
|
||||
const awardsToSubmit = professor.professorId ? this.selectedProfessorAwards : this.newProfessorAwards;
|
||||
if (awardsToSubmit && awardsToSubmit.length > 0) {
|
||||
awardsToSubmit.forEach((award, index) => {
|
||||
if (award.title && award.year) { // Only include awards with title and year
|
||||
formData.append(`awards[${index}].title`, award.title);
|
||||
formData.append(`awards[${index}].year`, award.year);
|
||||
formData.append(`awards[${index}].description`, award.description || '');
|
||||
formData.append(`awards[${index}].imageUrl`, award.imageUrl || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Profile image
|
||||
if (profileImage) {
|
||||
formData.append('profileImage', profileImage);
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
public onDeleteProfessor(professor: Professor): void {
|
||||
this.subs.sink = this.professorService.deleteProfessor(professor.professorId).subscribe(
|
||||
(response: CustomHttpResponse) => {
|
||||
this.getProfessors(false);
|
||||
this.invalidateVariables();
|
||||
this.notificationService.notify(NotificationType.SUCCESS, response.message);
|
||||
},
|
||||
(errorResponse: HttpErrorResponse) => {
|
||||
this.sendErrorNotification(errorResponse.error.message);
|
||||
}
|
||||
);
|
||||
if (confirm(`Are you sure you want to delete ${professor.firstName} ${professor.lastName}?`)) {
|
||||
this.subs.sink = this.professorService.deleteProfessor(professor.professorId).subscribe(
|
||||
(response: CustomHttpResponse) => {
|
||||
this.getProfessors(false);
|
||||
this.invalidateVariables();
|
||||
this.notificationService.notify(NotificationType.SUCCESS, response.message);
|
||||
},
|
||||
(errorResponse: HttpErrorResponse) => {
|
||||
this.sendErrorNotification(errorResponse.error.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public updateProfileImage(): void {
|
||||
@ -241,8 +392,9 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
break;
|
||||
case HttpEventType.Response:
|
||||
if (event.status === 200) {
|
||||
// For browser to fetch image when updating (because name left the same)
|
||||
this.loggedInProfessor.profileImageUrl = `${event.body.profileImageUrl}?time=${new Date().getTime()}`;
|
||||
if (this.loggedInProfessor) {
|
||||
this.loggedInProfessor.profileImageUrl = `${event.body.profileImageUrl}?time=${new Date().getTime()}`;
|
||||
}
|
||||
this.notificationService.notify(NotificationType.SUCCESS, `${event.body.firstName}'s image updated successfully`);
|
||||
this.fileUploadStatus.status = 'done';
|
||||
} else {
|
||||
@ -255,10 +407,36 @@ export class ProfessorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public get isAdmin(): boolean {
|
||||
return this.loggedInUser.role === Role.ADMIN || this.loggedInUser.role === Role.SUPER_ADMIN;
|
||||
return this.loggedInUser && (this.loggedInUser.role === Role.ADMIN || this.loggedInUser.role === Role.SUPER_ADMIN);
|
||||
}
|
||||
|
||||
public get isManager(): boolean {
|
||||
return this.isAdmin || this.loggedInUser.role === Role.MANAGER;
|
||||
return this.isAdmin || (this.loggedInUser && this.loggedInUser.role === Role.MANAGER);
|
||||
}
|
||||
}
|
||||
|
||||
public clearFormData(): void {
|
||||
this.selectedProfessor = {
|
||||
professorId: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
profileImageUrl: '',
|
||||
status: WorkingStatus.ACTIVE,
|
||||
category: ProfessorCategory.FACULTY,
|
||||
department: '',
|
||||
position: '',
|
||||
officeLocation: '',
|
||||
joinDate: new Date(),
|
||||
phone: '',
|
||||
specialty: '',
|
||||
certification: '',
|
||||
training: '',
|
||||
experience: '',
|
||||
description: '',
|
||||
designation: '',
|
||||
workDays: [],
|
||||
awards: []
|
||||
};
|
||||
this.invalidateVariables();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
// src/app/enum/professor-category.enum.ts
|
||||
export enum ProfessorCategory {
|
||||
FACULTY = 'FACULTY',
|
||||
SUPPORT_TEAM = 'SUPPORT_TEAM',
|
||||
TRAINEE_FELLOW = 'TRAINEE_FELLOW'
|
||||
}
|
||||
@ -1,16 +1,46 @@
|
||||
import { WorkingStatus } from "../enum/WorkingStatus";
|
||||
// src/app/model/Professor.ts
|
||||
import { WorkingStatus } from '../enum/WorkingStatus';
|
||||
import { ProfessorCategory } from '../enum/professor-category.enum';
|
||||
|
||||
export class Professor {
|
||||
// id : number;
|
||||
professorId: string; // UUID
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
department: string;
|
||||
position: string;
|
||||
officeLocation: string;
|
||||
status: WorkingStatus; // Assuming status is represented as a string in the DTO
|
||||
joinDate: Date; // LocalDateTime as a string
|
||||
profileImageUrl?: string; // Optional, URL to the profile image
|
||||
// profileImage?: File; // Optional, used for uploading profile images
|
||||
}
|
||||
export interface Professor {
|
||||
id?: number;
|
||||
professorId: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
department: string;
|
||||
position: string;
|
||||
officeLocation: string;
|
||||
joinDate: Date;
|
||||
profileImageUrl: string;
|
||||
status: WorkingStatus;
|
||||
category: ProfessorCategory;
|
||||
|
||||
// Additional fields for Next.js integration
|
||||
phone?: string;
|
||||
specialty?: string;
|
||||
certification?: string;
|
||||
training?: string;
|
||||
experience?: string;
|
||||
description?: string;
|
||||
designation?: string;
|
||||
workDays?: string[];
|
||||
|
||||
// Awards and skills
|
||||
awards?: Award[];
|
||||
skills?: Skill[];
|
||||
}
|
||||
|
||||
export interface Award {
|
||||
id?: number;
|
||||
title: string;
|
||||
year: string;
|
||||
description: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export interface Skill {
|
||||
id?: number;
|
||||
name: string;
|
||||
level?: number;
|
||||
}
|
||||
@ -1,27 +1,35 @@
|
||||
// post.model.ts
|
||||
export interface Blog {
|
||||
id?: number;
|
||||
title: string;
|
||||
content: string;
|
||||
professors: { id: number, name: string }[]; // Adjust this as per your entity structure
|
||||
tags: string[];
|
||||
isPosted: boolean;
|
||||
}
|
||||
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
// Update the Blog interface to match your backend
|
||||
export interface Blog {
|
||||
id?: number;
|
||||
title: string;
|
||||
content: string;
|
||||
professors: { id: number, firstName?: string, name?: string }[];
|
||||
tags: string[];
|
||||
posted: boolean; // Changed from isPosted to posted to match backend
|
||||
imageUrl?: string; // Add imageUrl field
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BlogService {
|
||||
private apiUrl = environment.apiUrl+'/api/posts'; // Updated to match the Spring Boot controller
|
||||
private apiUrl = environment.apiUrl + '/api/posts';
|
||||
private baseApiUrl = environment.apiUrl; // Base API URL for file uploads
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
// Fix the upload URL - use baseApiUrl instead of apiUrl
|
||||
uploadImage(file: File): Observable<any> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return this.http.post(`${this.baseApiUrl}/api/files/upload`, formData);
|
||||
}
|
||||
|
||||
// Get all blogs
|
||||
getBlogs(): Observable<Blog[]> {
|
||||
@ -47,4 +55,4 @@ export class BlogService {
|
||||
deleteBlog(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.apiUrl}/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
support-portal-frontend/src/app/service/career.service.ts
Normal file
94
support-portal-frontend/src/app/service/career.service.ts
Normal file
@ -0,0 +1,94 @@
|
||||
// career.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
export interface Job {
|
||||
id?: number;
|
||||
title: string;
|
||||
department: string;
|
||||
location: string;
|
||||
type: string;
|
||||
experience: string;
|
||||
salary: string;
|
||||
description: string;
|
||||
requirements: string[];
|
||||
responsibilities: string[];
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface JobApplication {
|
||||
id?: number;
|
||||
jobId: number;
|
||||
fullName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
experience: string;
|
||||
coverLetter?: string;
|
||||
resumeUrl?: string;
|
||||
status?: string;
|
||||
job?: Job;
|
||||
createdDate?: string; // Add this line
|
||||
updatedDate?: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CareerService {
|
||||
private jobsApiUrl = environment.apiUrl + '/api/jobs';
|
||||
private applicationsApiUrl = environment.apiUrl + '/api/job-applications';
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
// Job management
|
||||
getAllJobs(): Observable<Job[]> {
|
||||
return this.http.get<Job[]>(this.jobsApiUrl);
|
||||
}
|
||||
|
||||
getActiveJobs(): Observable<Job[]> {
|
||||
return this.http.get<Job[]>(`${this.jobsApiUrl}/active`);
|
||||
}
|
||||
|
||||
getJob(id: number): Observable<Job> {
|
||||
return this.http.get<Job>(`${this.jobsApiUrl}/${id}`);
|
||||
}
|
||||
|
||||
createJob(job: Job): Observable<Job> {
|
||||
return this.http.post<Job>(this.jobsApiUrl, job);
|
||||
}
|
||||
|
||||
updateJob(id: number, job: Job): Observable<Job> {
|
||||
return this.http.put<Job>(`${this.jobsApiUrl}/${id}`, job);
|
||||
}
|
||||
|
||||
deleteJob(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.jobsApiUrl}/${id}`);
|
||||
}
|
||||
|
||||
// Job application management
|
||||
getAllApplications(): Observable<JobApplication[]> {
|
||||
return this.http.get<JobApplication[]>(this.applicationsApiUrl);
|
||||
}
|
||||
|
||||
getApplicationsByJobId(jobId: number): Observable<JobApplication[]> {
|
||||
return this.http.get<JobApplication[]>(`${this.applicationsApiUrl}/job/${jobId}`);
|
||||
}
|
||||
|
||||
getApplication(id: number): Observable<JobApplication> {
|
||||
return this.http.get<JobApplication>(`${this.applicationsApiUrl}/${id}`);
|
||||
}
|
||||
|
||||
createApplication(application: JobApplication): Observable<JobApplication> {
|
||||
return this.http.post<JobApplication>(this.applicationsApiUrl, application);
|
||||
}
|
||||
|
||||
updateApplicationStatus(id: number, status: string): Observable<void> {
|
||||
return this.http.put<void>(`${this.applicationsApiUrl}/${id}/status?status=${status}`, {});
|
||||
}
|
||||
|
||||
deleteApplication(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.applicationsApiUrl}/${id}`);
|
||||
}
|
||||
}
|
||||
98
support-portal-frontend/src/app/service/education.service.ts
Normal file
98
support-portal-frontend/src/app/service/education.service.ts
Normal file
@ -0,0 +1,98 @@
|
||||
// education.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
export interface Course {
|
||||
id?: number;
|
||||
title: string;
|
||||
description: string;
|
||||
duration: string;
|
||||
seats: number;
|
||||
category: string;
|
||||
level: string;
|
||||
instructor: string;
|
||||
price?: string;
|
||||
startDate?: string;
|
||||
imageUrl?: string;
|
||||
eligibility: string[];
|
||||
objectives: string[];
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface CourseApplication {
|
||||
id?: number;
|
||||
courseId: number;
|
||||
fullName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
qualification: string;
|
||||
experience?: string;
|
||||
coverLetter?: string;
|
||||
resumeUrl?: string;
|
||||
status?: string;
|
||||
course?: Course;
|
||||
createdDate?: string;
|
||||
updatedDate?: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EducationService {
|
||||
private coursesApiUrl = environment.apiUrl + '/api/courses';
|
||||
private applicationsApiUrl = environment.apiUrl + '/api/course-applications';
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
// Course management
|
||||
getAllCourses(): Observable<Course[]> {
|
||||
return this.http.get<Course[]>(this.coursesApiUrl);
|
||||
}
|
||||
|
||||
getActiveCourses(): Observable<Course[]> {
|
||||
return this.http.get<Course[]>(`${this.coursesApiUrl}/active`);
|
||||
}
|
||||
|
||||
getCourse(id: number): Observable<Course> {
|
||||
return this.http.get<Course>(`${this.coursesApiUrl}/${id}`);
|
||||
}
|
||||
|
||||
createCourse(course: Course): Observable<Course> {
|
||||
return this.http.post<Course>(this.coursesApiUrl, course);
|
||||
}
|
||||
|
||||
updateCourse(id: number, course: Course): Observable<Course> {
|
||||
return this.http.put<Course>(`${this.coursesApiUrl}/${id}`, course);
|
||||
}
|
||||
|
||||
deleteCourse(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.coursesApiUrl}/${id}`);
|
||||
}
|
||||
|
||||
// Course application management
|
||||
getAllApplications(): Observable<CourseApplication[]> {
|
||||
return this.http.get<CourseApplication[]>(this.applicationsApiUrl);
|
||||
}
|
||||
|
||||
getApplicationsByCourseId(courseId: number): Observable<CourseApplication[]> {
|
||||
return this.http.get<CourseApplication[]>(`${this.applicationsApiUrl}/course/${courseId}`);
|
||||
}
|
||||
|
||||
getApplication(id: number): Observable<CourseApplication> {
|
||||
return this.http.get<CourseApplication>(`${this.applicationsApiUrl}/${id}`);
|
||||
}
|
||||
|
||||
createApplication(application: CourseApplication): Observable<CourseApplication> {
|
||||
return this.http.post<CourseApplication>(this.applicationsApiUrl, application);
|
||||
}
|
||||
|
||||
updateApplicationStatus(id: number, status: string): Observable<void> {
|
||||
return this.http.put<void>(`${this.applicationsApiUrl}/${id}/status?status=${status}`, {});
|
||||
}
|
||||
|
||||
deleteApplication(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.applicationsApiUrl}/${id}`);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { environment } from 'src/environments/environment';
|
||||
@ -8,14 +8,80 @@ import { environment } from 'src/environments/environment';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EventService {
|
||||
private apiUrl = environment.apiUrl+'/api/events'; // Replace with your API endpoint
|
||||
private apiUrl = environment.apiUrl + '/api/events';
|
||||
private fileUploadUrl = environment.apiUrl + '/api/files';
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
private httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
})
|
||||
};
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
console.log('EventService initialized with API URL:', this.apiUrl);
|
||||
}
|
||||
|
||||
// Upload single image file
|
||||
uploadImage(file: File): Observable<any> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
console.log('Uploading image:', file.name);
|
||||
|
||||
// Don't set Content-Type header for FormData - let browser set it
|
||||
return this.http.post<any>(`${this.fileUploadUrl}/upload`, formData)
|
||||
.pipe(
|
||||
map(response => {
|
||||
console.log('Image uploaded successfully:', response);
|
||||
return response;
|
||||
}),
|
||||
catchError(this.handleError<any>('uploadImage'))
|
||||
);
|
||||
}
|
||||
|
||||
// Upload multiple images
|
||||
uploadMultipleImages(files: File[]): Observable<any[]> {
|
||||
const uploadPromises = files.map(file =>
|
||||
this.uploadImage(file).toPromise()
|
||||
);
|
||||
|
||||
return new Observable(observer => {
|
||||
Promise.all(uploadPromises)
|
||||
.then(results => {
|
||||
observer.next(results);
|
||||
observer.complete();
|
||||
})
|
||||
.catch(error => {
|
||||
observer.error(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Delete image by filename
|
||||
deleteImage(filename: string): Observable<any> {
|
||||
const url = `${this.fileUploadUrl}/images/${filename}`;
|
||||
console.log('Deleting image:', url);
|
||||
|
||||
return this.http.delete<any>(url)
|
||||
.pipe(
|
||||
map(response => {
|
||||
console.log('Image deleted successfully');
|
||||
return response;
|
||||
}),
|
||||
catchError(this.handleError<any>('deleteImage'))
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch all events
|
||||
getEvents(): Observable<any[]> {
|
||||
return this.http.get<any[]>(this.apiUrl)
|
||||
console.log('Fetching events from:', this.apiUrl);
|
||||
return this.http.get<any[]>(this.apiUrl, this.httpOptions)
|
||||
.pipe(
|
||||
map(events => {
|
||||
console.log('Events received:', events);
|
||||
return events;
|
||||
}),
|
||||
catchError(this.handleError<any[]>('getEvents', []))
|
||||
);
|
||||
}
|
||||
@ -23,16 +89,26 @@ export class EventService {
|
||||
// Fetch a single event by id
|
||||
getEvent(id: number): Observable<any> {
|
||||
const url = `${this.apiUrl}/${id}`;
|
||||
return this.http.get<any>(url)
|
||||
console.log('Fetching event from:', url);
|
||||
return this.http.get<any>(url, this.httpOptions)
|
||||
.pipe(
|
||||
map(event => {
|
||||
console.log('Event received:', event);
|
||||
return event;
|
||||
}),
|
||||
catchError(this.handleError<any>(`getEvent id=${id}`))
|
||||
);
|
||||
}
|
||||
|
||||
// Create a new event
|
||||
createEvent(event: any): Observable<any> {
|
||||
console.log('Creating event:', event);
|
||||
return this.http.post<any>(this.apiUrl, event, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => {
|
||||
console.log('Event created successfully:', response);
|
||||
return response;
|
||||
}),
|
||||
catchError(this.handleError<any>('createEvent'))
|
||||
);
|
||||
}
|
||||
@ -40,8 +116,13 @@ export class EventService {
|
||||
// Update an existing event
|
||||
updateEvent(id: number, event: any): Observable<any> {
|
||||
const url = `${this.apiUrl}/${id}`;
|
||||
console.log('Updating event at:', url, 'with data:', event);
|
||||
return this.http.put<any>(url, event, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => {
|
||||
console.log('Event updated successfully:', response);
|
||||
return response;
|
||||
}),
|
||||
catchError(this.handleError<any>('updateEvent'))
|
||||
);
|
||||
}
|
||||
@ -49,20 +130,53 @@ export class EventService {
|
||||
// Delete an event by id
|
||||
deleteEvent(id: number): Observable<any> {
|
||||
const url = `${this.apiUrl}/${id}`;
|
||||
return this.http.delete<any>(url)
|
||||
console.log('Deleting event at:', url);
|
||||
return this.http.delete<any>(url, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => {
|
||||
console.log('Event deleted successfully');
|
||||
return response;
|
||||
}),
|
||||
catchError(this.handleError<any>('deleteEvent'))
|
||||
);
|
||||
}
|
||||
|
||||
private httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||
};
|
||||
// Test connection to API
|
||||
testConnection(): Observable<any> {
|
||||
console.log('Testing connection to:', this.apiUrl);
|
||||
return this.http.get<any>(this.apiUrl, this.httpOptions)
|
||||
.pipe(
|
||||
map(response => {
|
||||
console.log('Connection test successful:', response);
|
||||
return { success: true, data: response };
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Connection test failed:', error);
|
||||
return of({ success: false, error: error });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private handleError<T>(operation = 'operation', result?: T) {
|
||||
return (error: any): Observable<T> => {
|
||||
console.error(`${operation} failed: ${error.message}`);
|
||||
console.error(`${operation} failed:`, error);
|
||||
|
||||
// Detailed error logging
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
if (error.status === 0) {
|
||||
console.error('Network error - possible CORS issue or server not running');
|
||||
console.error('Check if Spring Boot server is running on:', this.apiUrl);
|
||||
} else if (error.status === 404) {
|
||||
console.error('API endpoint not found:', error.url);
|
||||
} else if (error.status === 500) {
|
||||
console.error('Server error:', error.error);
|
||||
} else {
|
||||
console.error('HTTP error:', error.status, error.statusText);
|
||||
}
|
||||
}
|
||||
|
||||
// Return empty result to keep app running
|
||||
return of(result as T);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
// file-upload.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
export interface FileUploadResponse {
|
||||
url: string;
|
||||
filename: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FileUploadService {
|
||||
private apiUrl = environment.apiUrl + '/api/files';
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
uploadFile(file: File): Observable<FileUploadResponse> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return this.http.post<FileUploadResponse>(`${this.apiUrl}/upload`, formData);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
// upcoming-events.service.ts
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
export interface UpcomingEvent {
|
||||
id?: number;
|
||||
title: string;
|
||||
description: string;
|
||||
schedule: string;
|
||||
eventDate?: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UpcomingEventsService {
|
||||
private apiUrl = environment.apiUrl + '/api/upcoming-events';
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getAllUpcomingEvents(): Observable<UpcomingEvent[]> {
|
||||
return this.http.get<UpcomingEvent[]>(this.apiUrl);
|
||||
}
|
||||
|
||||
getActiveUpcomingEvents(): Observable<UpcomingEvent[]> {
|
||||
return this.http.get<UpcomingEvent[]>(`${this.apiUrl}/active`);
|
||||
}
|
||||
|
||||
getUpcomingEvent(id: number): Observable<UpcomingEvent> {
|
||||
return this.http.get<UpcomingEvent>(`${this.apiUrl}/${id}`);
|
||||
}
|
||||
|
||||
createUpcomingEvent(event: UpcomingEvent): Observable<UpcomingEvent> {
|
||||
return this.http.post<UpcomingEvent>(this.apiUrl, event);
|
||||
}
|
||||
|
||||
updateUpcomingEvent(id: number, event: UpcomingEvent): Observable<UpcomingEvent> {
|
||||
return this.http.put<UpcomingEvent>(`${this.apiUrl}/${id}`, event);
|
||||
}
|
||||
|
||||
deleteUpcomingEvent(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.apiUrl}/${id}`);
|
||||
}
|
||||
}
|
||||
@ -9,8 +9,8 @@ export const environment = {
|
||||
// apiUrl: 'http://portal-bean.shyshkin.net',
|
||||
// apiUrl: 'http://supportportalbackend-env.eba-wfr5wya3.eu-north-1.elasticbeanstalk.com',
|
||||
// apiUrl: 'http://support-portal.shyshkin.net:5000',
|
||||
// apiUrl: 'http://localhost:8080',
|
||||
apiUrl: 'https://cncbackend.techzoos.in',
|
||||
apiUrl: 'http://localhost:8080',
|
||||
// apiUrl: 'https://cncbackend.techzoos.in',
|
||||
|
||||
publicUrls: ['/user/login', '/user/register', '/user/*/profile-image', '/user/*/profile-image/**']
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user