Update with new components

This commit is contained in:
2025-09-23 19:41:25 +05:30
parent cfe68a276f
commit bd2f5b95ce
129 changed files with 5849 additions and 1169 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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] },
{

View File

@ -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,

View File

@ -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 {}

View File

@ -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,

View File

@ -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">&#10003;</span> <!-- Check mark for posted -->
<span *ngIf="!blog.posted" class="text-danger">&#10007;</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">&#10003;</span>
<!-- Check mark for posted -->
<span *ngIf="!blog.posted" class="text-danger">&#10007;</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>

View File

@ -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 '';
}
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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();
});
});

View File

@ -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;
}}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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();
}
}

View File

@ -0,0 +1,6 @@
// src/app/enum/professor-category.enum.ts
export enum ProfessorCategory {
FACULTY = 'FACULTY',
SUPPORT_TEAM = 'SUPPORT_TEAM',
TRAINEE_FELLOW = 'TRAINEE_FELLOW'
}

View File

@ -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;
}

View File

@ -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}`);
}
}
}

View 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}`);
}
}

View 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}`);
}
}

View File

@ -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);
};
}
}
}

View File

@ -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);
}
}

View File

@ -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}`);
}
}

View File

@ -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/**']
};