diff --git a/ToDo.yaml b/ToDo.yaml
index d0eb9cb..23c0bc9 100755
--- a/ToDo.yaml
+++ b/ToDo.yaml
@@ -12,9 +12,10 @@ Server:
Need to update notification email
Create page to notify cx that their work email has been verified
+ Verify that each resume section belongs to the resume it was created for:
+
Client:
jobs/editor:
- Job Listing Skills exists but isn't implimented in the UI
Want to add completed job listing preview at end of carosel
Resume:
@@ -27,6 +28,11 @@ Client:
Allow users to look up jobs and apply [ Boost visibility | Completely manual ]
Mark ghost listings to allow users to be informed and put companies on blast
+ resume/viewer:
+ CSS is broken
+ Company needs to be able to add notes to resume
+ Company needs to be able to add a rating to the resume
+
company/editor:
Keyboard Tab key does nothing
Format phone number for database
@@ -36,6 +42,10 @@ Client:
resume/editor:
There is no data validation
+ application/viewer:
+ need to make look better for companies to sort
+ need to show notes and rating properly
+
Company:
Need to impliment Add employee
Need to impliment Remove employee
diff --git a/src/Client/src/app/app.routes.ts b/src/Client/src/app/app.routes.ts
index b221d22..bd5275b 100644
--- a/src/Client/src/app/app.routes.ts
+++ b/src/Client/src/app/app.routes.ts
@@ -8,15 +8,21 @@ import { CompanyEditorComponent } from './pages/company/editor/editor.component'
import { JobViewerComponent } from './pages/jobs/viewer/jobviewer.component';
import { CompanyComponent } from './pages/company/company.component';
import { ResumesEditorComponent } from './pages/resumes/editor/editor.component';
+import { AppViewerComponent } from './pages/applicationviewer/appviewer.component';
+import { ResumesViewerComponent } from './pages/resumes/viewer/viewer.component';
export const routes: Routes = [
// Home
{ path: "", component: HomeComponent },
+ // Application
+ { path: "application/viewer", component: AppViewerComponent },
+
// Resumes
{ path: "resumes", component: ResumesComponent },
{ path: "resumes/editor", component: ResumesEditorComponent },
+ { path: "resumes/viewer", component: ResumesViewerComponent },
// Jobs
{ path: "jobs", component: JobsComponent },
diff --git a/src/Client/src/app/app.ts b/src/Client/src/app/app.ts
index 6d4ec07..5048ec6 100644
--- a/src/Client/src/app/app.ts
+++ b/src/Client/src/app/app.ts
@@ -20,7 +20,6 @@ export class App {
this.devMode = isDevMode();
this.route.queryParams.subscribe(params => {
this.loginToken = params['LoginToken'];
- console.log("LoginToken : " + this.loginToken);
if (this.loginToken){
this.http.post( "api/account/loginticket", JSON.stringify(this.loginToken), { headers: {'Content-Type': 'application/json'} } ).subscribe({
next: async() => {
diff --git a/src/Client/src/app/models/Application.ts b/src/Client/src/app/models/Application.ts
index 576fefd..73560f1 100644
--- a/src/Client/src/app/models/Application.ts
+++ b/src/Client/src/app/models/Application.ts
@@ -9,4 +9,5 @@ export class Application {
public hasBeenViewed: boolean = false;
public rating: number = 0;
public notes: string = "";
+ public trackUUID: string = crypto.randomUUID();
}
\ No newline at end of file
diff --git a/src/Client/src/app/models/Company.ts b/src/Client/src/app/models/Company.ts
index a2e4a6a..1d34666 100644
--- a/src/Client/src/app/models/Company.ts
+++ b/src/Client/src/app/models/Company.ts
@@ -13,4 +13,5 @@ export class Company {
public stateOrRegion: string = "";
public city: string = "";
public description: string = "";
+ public trackUUID: string = crypto.randomUUID();
}
\ No newline at end of file
diff --git a/src/Client/src/app/models/Employee.ts b/src/Client/src/app/models/Employee.ts
index b62a5ff..127bba2 100644
--- a/src/Client/src/app/models/Employee.ts
+++ b/src/Client/src/app/models/Employee.ts
@@ -6,4 +6,5 @@ export class Employee {
public accountName: string = "";
public accountEmail: string = "";
public company: Company = new Company;
+ public trackUUID: string = crypto.randomUUID();
}
\ No newline at end of file
diff --git a/src/Client/src/app/models/JobListing.ts b/src/Client/src/app/models/JobListing.ts
index ffd34e3..778d3b6 100644
--- a/src/Client/src/app/models/JobListing.ts
+++ b/src/Client/src/app/models/JobListing.ts
@@ -15,11 +15,13 @@ export class JobListing {
public createdTime: Date = new Date();
public modifiedTime: Date = new Date();
public isDeleted: boolean = false;
+ public trackUUID: string = crypto.randomUUID();
}
export class JobListingSkills {
public id: number | null = null;
public jobListingID: number = 0;
public name: string = "";
- public Description: string = "";
+ public description: string = "";
+ public trackUUID: string = crypto.randomUUID();
}
\ No newline at end of file
diff --git a/src/Client/src/app/pages/applicationviewer/appviewer.component.css b/src/Client/src/app/pages/applicationviewer/appviewer.component.css
new file mode 100644
index 0000000..8eecdb8
--- /dev/null
+++ b/src/Client/src/app/pages/applicationviewer/appviewer.component.css
@@ -0,0 +1,134 @@
+button {
+ height: 45px;
+ border-radius: 5px;
+ margin: 10px;
+ text-align: center;
+ padding: 15px 20px;
+ transition: 0.5s;
+ background-color: #00000000;
+ border: 1px solid var(--Mistox-Black);
+ color: var(--Mistox-Black);
+ text-decoration: none;
+ font: inherit;
+}
+
+ button:hover {
+ background-color: #00000044;
+ color: var(--Mistox-Light);
+ }
+
+.top-bar {
+ width: 100%;
+ height: 60px;
+}
+
+.content-frame {
+ background-color: #3c3c3c;
+ width: calc(100% - 40px);
+ height: calc(100vh - 400px);
+ border-radius: 20px;
+ margin: 10px;
+ overflow: scroll;
+ padding: 10px;
+ color: var(--Mistox-White);
+}
+
+.center-item {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+}
+
+.content-edit {
+ position: absolute;
+ right: 20px;
+}
+
+.center-item img {
+ width: 300px;
+}
+
+.content-name {
+ width: 300px;
+ text-align: center;
+ font-size: 30px;
+}
+
+.content-name h1 {
+ margin: 0;
+}
+
+.content-link {
+ display: flex;
+ width: 300px;
+ justify-content: center;
+}
+
+.content-link a {
+ text-decoration: none;
+ color: var(--Mistox-White);
+ margin-top: auto;
+}
+
+.content-desc {
+ border: solid 1px red;
+ border-radius: 5px;
+ padding: 20px;
+ margin: 0 100px;
+ margin-bottom: 50px;
+}
+
+.content-desc h1 {
+ margin: 0;
+ font-size: 20px;
+ color: #ddd;
+}
+
+.content-button {
+ display: flex;
+ justify-content: center;
+}
+
+.content-button span {
+ align-content: center;
+}
+
+.split-frame {
+ display: flex;
+ width: 100%;
+}
+
+.half-frame {
+ width: 50%;
+ border-right: solid 1px var(--Mistox-Black);
+ border-left: solid 1px var(--Mistox-Black);
+}
+
+.half-frame h2 {
+ text-align: center;
+}
+
+.job-tile {
+ display: flex;
+ background-color: var(--Mistox-Black);
+ justify-content: end;
+ align-items: center;
+ border-radius: 10px;
+ margin: 0 5px;
+ margin-bottom: 10px;
+}
+
+.center-text {
+ display: flex;
+ flex: 1;
+ justify-content: center;
+}
+
+.job-tile h1 {
+ margin: 0;
+}
+
+.job-tile button {
+ color: white;
+ border-color: white;
+}
\ No newline at end of file
diff --git a/src/Client/src/app/pages/applicationviewer/appviewer.component.html b/src/Client/src/app/pages/applicationviewer/appviewer.component.html
new file mode 100644
index 0000000..487c6ff
--- /dev/null
+++ b/src/Client/src/app/pages/applicationviewer/appviewer.component.html
@@ -0,0 +1,15 @@
+
+ @for (application of List; track application.trackUUID){
+
+
{{ application.responseEmail }}
+ {{ application.notes }}
+ {{ application.hasBeenViewed }}
+ {{ application.rating }}
+ {{ application.responseStatus }}
+
+ Date Applied:
{{ application.dateApplied }}
+
+
+
+ }
+
\ No newline at end of file
diff --git a/src/Client/src/app/pages/applicationviewer/appviewer.component.ts b/src/Client/src/app/pages/applicationviewer/appviewer.component.ts
new file mode 100644
index 0000000..6de59ae
--- /dev/null
+++ b/src/Client/src/app/pages/applicationviewer/appviewer.component.ts
@@ -0,0 +1,48 @@
+import { Component } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { FormsModule } from '@angular/forms';
+import { Router, ActivatedRoute, RouterModule } from '@angular/router';
+import { Title } from '@angular/platform-browser';
+import { CommonModule } from '@angular/common';
+import { Authentication } from 'app/services/Authentication';
+import { Application } from 'app/models/Application';
+
+@Component({
+ selector: 'App-Viewer',
+ templateUrl: './appviewer.component.html',
+ styleUrls: [ './appviewer.component.css' ],
+ imports: [ FormsModule, CommonModule, RouterModule ]
+})
+export class AppViewerComponent {
+ public ErrorMsg: string = "";
+
+ public List: Application[] = [];
+
+ constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
+ this.title.setTitle("Applications | BoredCareers");
+ if (!auth.isLoggedIn){
+ router.navigate(["/"]);
+ }
+ };
+
+ ngOnInit(){
+ this.route.queryParams.subscribe(params => {
+ const JobListingID = params['JobID'] ? +params['JobID'] : null;
+ if (JobListingID !== null){
+ this.http.get("api/application?JobListingID=" + JobListingID).subscribe({
+ next: data => {
+ this.List = data;
+ },
+ error: err => {
+ this.ErrorMsg = err.error;
+ }
+ });
+ }
+ });
+ }
+
+ viewResume(app: Application) {
+ window.open('/resumes/viewer?ResumeID=' + app.resumeID, '_blank');
+ }
+
+}
\ No newline at end of file
diff --git a/src/Client/src/app/pages/company/company.component.html b/src/Client/src/app/pages/company/company.component.html
index e299ab6..a22e49b 100644
--- a/src/Client/src/app/pages/company/company.component.html
+++ b/src/Client/src/app/pages/company/company.component.html
@@ -46,6 +46,7 @@
{{ listing.title }}
+
diff --git a/src/Client/src/app/pages/company/editor/editor.component.html b/src/Client/src/app/pages/company/editor/editor.component.html
index 53a4e56..8b85075 100644
--- a/src/Client/src/app/pages/company/editor/editor.component.html
+++ b/src/Client/src/app/pages/company/editor/editor.component.html
@@ -60,11 +60,11 @@
diff --git a/src/Client/src/app/pages/company/editor/editor.component.ts b/src/Client/src/app/pages/company/editor/editor.component.ts
index e0b5b60..41eab5e 100644
--- a/src/Client/src/app/pages/company/editor/editor.component.ts
+++ b/src/Client/src/app/pages/company/editor/editor.component.ts
@@ -6,6 +6,7 @@ import { Title } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { Authentication } from 'app/services/Authentication';
import { Company } from 'app/models/Company';
+import { Validation } from 'app/services/Validation';
@Component({
selector: 'main-company-editor',
@@ -23,7 +24,7 @@ export class CompanyEditorComponent {
public ErrorMsg: string = "";
MaxFileMB: number = 3;
- constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
+ constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication, public validator: Validation ) {
this.title.setTitle("Company - Editor | BoredCareers");
if (!auth.isLoggedIn){
router.navigate(["/"]);
@@ -54,6 +55,18 @@ export class CompanyEditorComponent {
this.updateUI(0);
}
+ validatePhone(input: string){
+ let result = this.validator.validatePhoneNumber(input);
+ this.newListing.phone = result[1];
+ }
+
+ validateEmail(input: string){
+ let result = this.validator.validateEmail(input);
+ if (result[0]){
+ this.newListing.email = result[1];
+ }
+ }
+
@HostListener('window:keydown', ['$event'])
handleGlobalKeyDown(event: KeyboardEvent){
if ( event.key === 'Tab' ){
@@ -122,51 +135,73 @@ export class CompanyEditorComponent {
PostNewCompany(company: Company){
if (this.isNullOrEmpty(company.name)){
+ this.ErrorMsg = "Comany name is blank";
this.focusFrame(0, 0);
return;
}
if (this.isNullOrEmpty(company.websiteURL)){
+ this.ErrorMsg = "Website URL is blank";
this.focusFrame(1, 0);
return;
}
if (this.isNullOrEmpty(company.logo)){
+ this.ErrorMsg = "Logo is blank";
this.focusFrame(2, 0);
return;
}
if (this.isNullOrEmpty(company.email)){
+ this.ErrorMsg = "Email is blank";
+ this.focusFrame(3, 0);
+ return;
+ }
+
+ if (this.validator.validateEmail(company.email)[0]){
+ this.ErrorMsg = "Email is invalid";
this.focusFrame(3, 0);
return;
}
if (this.isNullOrEmpty(company.phone)){
+ this.ErrorMsg = "Phone number is blank";
+ this.focusFrame(3, 1);
+ return;
+ }
+
+ if (this.validator.validatePhoneNumber(company.phone)[0]){
+ this.ErrorMsg = "Phone number is invalid";
this.focusFrame(3, 1);
return;
}
if (this.isNullOrEmpty(company.city)){
+ this.ErrorMsg = "City is blank";
this.focusFrame(4, 0);
return;
}
if (this.isNullOrEmpty(company.country)){
+ this.ErrorMsg = "Country is blank";
this.focusFrame(4, 1);
return;
}
if (this.isNullOrEmpty(company.stateOrRegion)){
+ this.ErrorMsg = "State or Region is blank";
this.focusFrame(4, 2);
return;
}
if (this.isNullOrEmpty(company.postalCode)){
+ this.ErrorMsg = "Postal Code is blank";
this.focusFrame(4, 3);
return;
}
if (this.isNullOrEmpty(company.description)){
+ this.ErrorMsg = "Description is blank";
this.focusFrame(5, 0);
return;
}
diff --git a/src/Client/src/app/pages/jobs/editor/jobeditor.component.html b/src/Client/src/app/pages/jobs/editor/jobeditor.component.html
index ef9f48e..93e47a4 100644
--- a/src/Client/src/app/pages/jobs/editor/jobeditor.component.html
+++ b/src/Client/src/app/pages/jobs/editor/jobeditor.component.html
@@ -91,6 +91,27 @@
+
+
+
+
+
+
+
+ @for(skill of Listing.skills; track skill.trackUUID){
+
+
+
+
+
+ }
+
+
+
+
+
+
+
diff --git a/src/Client/src/app/pages/jobs/editor/jobeditor.component.ts b/src/Client/src/app/pages/jobs/editor/jobeditor.component.ts
index df01516..7a77aa3 100644
--- a/src/Client/src/app/pages/jobs/editor/jobeditor.component.ts
+++ b/src/Client/src/app/pages/jobs/editor/jobeditor.component.ts
@@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms';
import { Router, ActivatedRoute, RouterModule } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
-import { JobListing } from 'app/models/JobListing';
+import { JobListing, JobListingSkills } from 'app/models/JobListing';
import { Authentication } from 'app/services/Authentication';
@Component({
@@ -51,6 +51,9 @@ export class JobEditorComponent {
if (this.mode === "edit") {
this.http.get
("api/joblisting/" + JobID).subscribe({
next: data => {
+ data.skills.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ });
this.Listing = data;
},
error: err => {
@@ -80,6 +83,20 @@ export class JobEditorComponent {
});
}
+ addSkill(){
+ this.Listing.skills.push(new JobListingSkills);
+ }
+
+ remSkill(self: JobListingSkills){
+ for(let i=0; i{{ selectedJob.country }}
{{ selectedJob.postalCode }}
+
+
Required Skills
+ @for(skill of selectedJob.skills; track skill.trackUUID){
+
+
{{ skill.name }}
+ {{ skill.description }}
+
+ }
+
+
{{ selectedJob.description }}
+
+ @for(resume of myResumes; track resume.trackUUID){
+
+
+
+ }
+
}
\ No newline at end of file
diff --git a/src/Client/src/app/pages/jobs/viewer/jobviewer.component.ts b/src/Client/src/app/pages/jobs/viewer/jobviewer.component.ts
index e22ae7f..452db25 100644
--- a/src/Client/src/app/pages/jobs/viewer/jobviewer.component.ts
+++ b/src/Client/src/app/pages/jobs/viewer/jobviewer.component.ts
@@ -7,6 +7,8 @@ import { CommonModule } from '@angular/common';
import { Authentication } from 'app/services/Authentication';
import { JobListing } from 'app/models/JobListing';
import { Company } from 'app/models/Company';
+import { Resume } from 'app/models/Resume';
+import { Application } from 'app/models/Application';
@Component({
selector: 'main-jobs-viewer',
@@ -17,9 +19,12 @@ import { Company } from 'app/models/Company';
export class JobViewerComponent {
public selectedJob: JobListing | null = null;
+ public myResumes: Resume[] = [];
public jobsCompany: Company | null = null;
public ErrorMsg: string = "";
+ JobListingID: number = -1;
+
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
this.title.setTitle("Jobs - Viewer | BoredCareers");
};
@@ -27,7 +32,8 @@ export class JobViewerComponent {
ngOnInit(){
this.route.queryParams.subscribe(params => {
const JobID = params['JobID'];
- if (JobID){
+ if (JobID) {
+ this.JobListingID = JobID;
this.http.get( "api/joblisting/" + JobID ).subscribe({
next: data => {
this.selectedJob = data;
@@ -51,6 +57,41 @@ export class JobViewerComponent {
}
});
+
+ this.http.get("api/resume").subscribe({
+ next: data => {
+ this.myResumes = data;
+ },
+ error: err => {
+ console.log("Error fetching resumes: " + err.error);
+ }
+ });
+ }
+
+ applyWithResume(resume: Resume) {
+ var application = new Application;
+
+ if (this.auth.loggedInUser.id != null){
+ application.accountID = this.auth.loggedInUser.id;
+ }
+
+ if (resume.id != null){
+ application.resumeID = resume.id;
+ }
+
+ application.jobListingID = this.JobListingID;
+ application.hasBeenViewed = false;
+ application.responseEmail = resume.email;
+
+ this.http.post("api/application", application).subscribe({
+ next: data => {
+ this.router.navigate(["/"]);
+ },
+ error: err => {
+ this.ErrorMsg = err.error;
+ }
+ });
+
}
}
\ No newline at end of file
diff --git a/src/Client/src/app/pages/resumes/editor/editor.component.html b/src/Client/src/app/pages/resumes/editor/editor.component.html
index 26833a1..c3ce681 100644
--- a/src/Client/src/app/pages/resumes/editor/editor.component.html
+++ b/src/Client/src/app/pages/resumes/editor/editor.component.html
@@ -23,8 +23,8 @@
diff --git a/src/Client/src/app/pages/resumes/editor/editor.component.ts b/src/Client/src/app/pages/resumes/editor/editor.component.ts
index df4bdfc..f4c8ee9 100644
--- a/src/Client/src/app/pages/resumes/editor/editor.component.ts
+++ b/src/Client/src/app/pages/resumes/editor/editor.component.ts
@@ -6,6 +6,7 @@ import { Title } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { Resume, ResumeCertification, ResumeEducation, ResumeExperience, ResumeExperienceBullet, ResumeLanguage, ResumeMilitary, ResumeMilitaryBullet, ResumeProject, ResumeSkill } from 'app/models/Resume';
import { Authentication } from 'app/services/Authentication';
+import { Validation } from 'app/services/Validation';
@Component({
selector: 'main-resume-editor',
@@ -20,7 +21,7 @@ export class ResumesEditorComponent {
public ErrorMsg: string = "";
- constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
+ constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication, public validator: Validation ) {
this.title.setTitle("Resume - Editor | BoredCareers");
if (!this.auth.isLoggedIn){
this.router.navigate(["/"]);
@@ -75,11 +76,6 @@ export class ResumesEditorComponent {
});
}
- // Pagnation //
- ////////////////////////////////
-
- ////////////////////////////////
-
SubmitForm(resume: Resume){
resume.accountID = this.auth.loggedInUser.id;
this.http.post("api/resume", resume).subscribe({
@@ -92,6 +88,20 @@ export class ResumesEditorComponent {
});
}
+ validatePhone(input: string){
+ let result = this.validator.validatePhoneNumber(input);
+ if (result[0]){
+ this.resume.phoneNumber = result[1];
+ }
+ }
+
+ validateEmail(input: string){
+ let result = this.validator.validateEmail(input);
+ if (result[0]){
+ this.resume.email = result[1];
+ }
+ }
+
PrintResume(){
const divToPrint = document.getElementsByClassName("paper")[0];
diff --git a/src/Client/src/app/pages/resumes/viewer/viewer.component.css b/src/Client/src/app/pages/resumes/viewer/viewer.component.css
new file mode 100644
index 0000000..be09ca3
--- /dev/null
+++ b/src/Client/src/app/pages/resumes/viewer/viewer.component.css
@@ -0,0 +1,105 @@
+input, textarea {
+ border: 1px solid #444;
+ background-color: transparent;
+}
+
+textarea {
+ resize: vertical;
+}
+
+h1 {
+ margin: 0;
+ font-size: 15px;
+}
+
+.paper {
+ width: 800px;
+ aspect-ratio: 17 / 22;
+ background: white;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+ margin: auto;
+ margin-top: 50px;
+ border: 1px solid #ddd;
+}
+
+.spacer {
+ margin: 0 10px;
+}
+
+.spacer-title {
+ display: flex;
+ margin: 10px 0;
+}
+
+.columns {
+ columns: 2;
+ column-gap: 10px;
+}
+
+.resume-header {
+ width: 70vw;
+ background: white;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+ margin: auto;
+ margin-top: 50px;
+ border: 1px solid #ddd;
+}
+
+.resume-section {
+ margin-bottom: 10px;
+}
+
+.resume-sub-section {
+ border: 1px solid #666666;
+ break-inside: avoid;
+ padding: 10px;
+}
+
+.title-text {
+ margin: 0;
+ height: 20px;
+}
+
+.header-left {
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+.header-right {
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-end;
+}
+
+.header-location input{
+ width: 80px;
+}
+
+.Add {
+ position: relative;
+ float: right;
+ width: 20px;
+ height: 20px;
+ background-color: #0F0;
+ border: none;
+ bottom: 20px;
+}
+
+.Del {
+ position: relative;
+ float: right;
+ width: 20px;
+ height: 20px;
+ background-color: #F00;
+ border: none;
+}
+
+.flex-two-row {
+ display: flex;
+ flex: 1;
+}
\ No newline at end of file
diff --git a/src/Client/src/app/pages/resumes/viewer/viewer.component.html b/src/Client/src/app/pages/resumes/viewer/viewer.component.html
new file mode 100644
index 0000000..cd65297
--- /dev/null
+++ b/src/Client/src/app/pages/resumes/viewer/viewer.component.html
@@ -0,0 +1,301 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Experience
+ @for(experience of resume.experiences; track experience.trackUUID ){
+
+ }
+
+
+
+ @if(resume.military !== null){
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Experience
+ @for(experience of resume.experiences; track experience.trackUUID ){
+
+ }
+
+
+
+ @if(resume.military !== null){
+
+ }
+
+
+
+
+
+
+
Skills
+ @for(skill of resume.skills; track skill.trackUUID){
+
+ }
+
+
+
+
+
+
+
+
+
Certifications
+ @for(cert of resume.certifications; track cert.trackUUID){
+
+ }
+
+
+
+
+
Projects
+ @for(proj of resume.projects; track proj.trackUUID){
+
+ }
+
+
+
+
+
+
+
Skills
+ @for(skill of resume.skills; track skill.trackUUID){
+
+ }
+
+
+
+
+
+
+
+
+
Certifications
+ @for(cert of resume.certifications; track cert.trackUUID){
+
+ }
+
+
+
+
+
Projects
+ @for(proj of resume.projects; track proj.trackUUID){
+
+ }
+
+
+
+
\ No newline at end of file
diff --git a/src/Client/src/app/pages/resumes/viewer/viewer.component.ts b/src/Client/src/app/pages/resumes/viewer/viewer.component.ts
new file mode 100644
index 0000000..b40feef
--- /dev/null
+++ b/src/Client/src/app/pages/resumes/viewer/viewer.component.ts
@@ -0,0 +1,92 @@
+import { Component } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { FormsModule } from '@angular/forms';
+import { Router, ActivatedRoute, RouterModule } from '@angular/router';
+import { Title } from '@angular/platform-browser';
+import { CommonModule } from '@angular/common';
+import { Resume } from 'app/models/Resume';
+import { Authentication } from 'app/services/Authentication';
+import { Validation } from 'app/services/Validation';
+
+@Component({
+ selector: 'main-resume-viewer',
+ templateUrl: './viewer.component.html',
+ styleUrls: [ './viewer.component.css' ],
+ imports: [FormsModule, CommonModule, RouterModule]
+})
+export class ResumesViewerComponent {
+
+ public resume: Resume = new Resume;
+
+ public ErrorMsg: string = "";
+
+ constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication, public validator: Validation ) {
+ this.title.setTitle("Resume - Viewer | BoredCareers");
+ if (!this.auth.isLoggedIn){
+ this.router.navigate(["/"]);
+ }
+ };
+
+ ngOnInit(){
+ this.route.queryParams.subscribe(params => {
+ const ResumeID = params['ResumeID'] ? +params['ResumeID'] : null;
+ if (ResumeID !== null){
+ this.http.get("api/resume?ResumeID=" + ResumeID).subscribe({
+ next: data => {
+
+ data.trackUUID = crypto.randomUUID();
+ data.certifications.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ });
+ data.educations.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ });
+ data.experiences.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ element.experienceBullets.forEach(subelement => {
+ subelement.trackUUID = crypto.randomUUID();
+ });
+ });
+ data.languages.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ });
+ if (data.military){
+ data.military.trackUUID = crypto.randomUUID();
+ data.military.militaryBullets.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ });
+ }
+ data.projects.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ });
+ data.skills.forEach(element => {
+ element.trackUUID = crypto.randomUUID();
+ });
+
+ this.resume = data;
+ },
+ error: err => {
+ this.ErrorMsg = err.error;
+ }
+ });
+ }
+ });
+ }
+
+ PrintResume(){
+ const divToPrint = document.getElementsByClassName("paper")[0];
+
+ const printContents = divToPrint.innerHTML;
+ const originalContents = document.body.innerHTML; // Store original body content
+
+ // Temporarily replace the body content with the div's content
+ document.body.innerHTML = printContents;
+
+ // Trigger the print dialog
+ window.print();
+
+ // Restore the original body content
+ document.body.innerHTML = originalContents;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Client/src/app/services/Validation.ts b/src/Client/src/app/services/Validation.ts
new file mode 100644
index 0000000..56314ea
--- /dev/null
+++ b/src/Client/src/app/services/Validation.ts
@@ -0,0 +1,35 @@
+import { Injectable } from "@angular/core";
+
+@Injectable({ providedIn: 'root' })
+export class Validation {
+
+ constructor(){ }
+
+ validatePhoneNumber(input: string): [boolean, string] {
+ var sanitized = input.replace(/\D/g, '');
+ if (sanitized.length < 10){
+ let formatted = sanitized.replace(/(\d{3})(?=\d{3})/g, '$1-').replace(/(\d{4})(?=\d{1,4}$)/, '$1-');
+ return [false, formatted];
+ } else if (sanitized.length === 10) {
+ let result = `(${sanitized.slice(0, 3)})${sanitized.slice(3, 6)}-${sanitized.slice(6, 10)}`;
+ return [true, result];
+ } else if (sanitized.length > 10 && sanitized.length < 14) {
+ let countryCode = sanitized.slice(0, sanitized.length - 10);
+ let areaCode = sanitized.slice(sanitized.length - 10, sanitized.length - 7);
+ let firstPart = sanitized.slice(sanitized.length - 7, sanitized.length - 4);
+ let secondPart = sanitized.slice(sanitized.length - 4);
+ let result = `+${countryCode} (${areaCode})${firstPart}-${secondPart}`;
+ return [true, result];
+ }else{
+ return [false, input];
+ }
+ }
+
+ emailRegex: RegExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
+ validateEmail(input: string): [boolean, string] {
+ const corrected = input.trim().toLowerCase();
+ const success = this.emailRegex.test(corrected);
+ return [success, corrected];
+ }
+
+}
\ No newline at end of file
diff --git a/src/Server/Controllers/ApplicationController.cs b/src/Server/Controllers/ApplicationController.cs
index 5d74675..1017332 100644
--- a/src/Server/Controllers/ApplicationController.cs
+++ b/src/Server/Controllers/ApplicationController.cs
@@ -10,13 +10,19 @@ namespace BoredCareers.Controllers {
public ApplicationController(DatabaseService db) : base(db) {}
[HttpGet]
- public async Task GetApplication(int ApplicationID) {
+ public async Task GetApplication(int? ApplicationID, int? JobListingID) {
if (isLoggedIn()) {
- Application? application = await _databaseService.GetApplication(ApplicationID);
- if (application != null) {
- return Ok(application);
+ if (ApplicationID != null) {
+ Application? application = await _databaseService.GetApplication(Convert.ToInt32(ApplicationID));
+ if (application != null) {
+ return Ok(application);
+ }
+ return NotFound("Application doesn't exist");
+ } else if (JobListingID != null) {
+ Application[] applications = await _databaseService.GetApplicationsFromJobListing(Convert.ToInt32(JobListingID));
+ return Ok(applications);
}
- return NotFound("Application doesn't exist");
+ return NotFound("No query selector supplied");
}
return NotFound("Not logged in");
}
diff --git a/src/Server/Services/DatabaseService/JobListing.cs b/src/Server/Services/DatabaseService/JobListing.cs
index f23b28a..8dad416 100644
--- a/src/Server/Services/DatabaseService/JobListing.cs
+++ b/src/Server/Services/DatabaseService/JobListing.cs
@@ -228,6 +228,8 @@ namespace BoredCareers.Services.DatabaseService {
Description = @Description,
ModifiedTime = @ModifiedTime,
IsDeleted = @IsDeleted;
+
+ SELECT LAST_INSERT_ID();
";
MySqlCommand cmd = new MySqlCommand(command, connection);
@@ -247,9 +249,17 @@ namespace BoredCareers.Services.DatabaseService {
cmd.Parameters.AddWithValue("@ModifiedTime", DateTime.UtcNow);
cmd.Parameters.AddWithValue("@IsDeleted", jobListing.IsDeleted);
- await cmd.ExecuteNonQueryAsync();
+ object? result = await cmd.ExecuteScalarAsync();
+ int jobListingID = 0;
+ if (jobListing.ID != null && jobListing.ID != 0) {
+ jobListingID = Convert.ToInt32(jobListing.ID);
+ } else {
+ cmd.CommandText = "";
+ jobListingID = Convert.ToInt32(result);
+ }
foreach (JobListingSkill cur in jobListing.Skills) {
+ cur.JobListingID = jobListingID;
await SetJobListingSkills(cur);
}
}
diff --git a/src/Server/Services/DatabaseService/JobListingSkill.cs b/src/Server/Services/DatabaseService/JobListingSkill.cs
index 953bc0b..3e20855 100644
--- a/src/Server/Services/DatabaseService/JobListingSkill.cs
+++ b/src/Server/Services/DatabaseService/JobListingSkill.cs
@@ -43,7 +43,7 @@ namespace BoredCareers.Services.DatabaseService {
using( MySqlConnection connection = GetConnection() ) {
await connection.OpenAsync();
string command = @"
- INSERT INTO JobListing
+ INSERT INTO JobListingSkill
(ID,JobListingID,Name,Description)
VALUES
(@ID,@JobListingID,@Name,@Description)