Merge pull request 'working' (#36) from working into main
Docker Build and Release Upload / build (push) Successful in 2m4s
Docker Build and Release Upload / build (push) Successful in 2m4s
Reviewed-on: #36
This commit was merged in pull request #36.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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() => {
|
||||
|
||||
@@ -9,4 +9,5 @@ export class Application {
|
||||
public hasBeenViewed: boolean = false;
|
||||
public rating: number = 0;
|
||||
public notes: string = "";
|
||||
public trackUUID: string = crypto.randomUUID();
|
||||
}
|
||||
@@ -13,4 +13,5 @@ export class Company {
|
||||
public stateOrRegion: string = "";
|
||||
public city: string = "";
|
||||
public description: string = "";
|
||||
public trackUUID: string = crypto.randomUUID();
|
||||
}
|
||||
@@ -6,4 +6,5 @@ export class Employee {
|
||||
public accountName: string = "";
|
||||
public accountEmail: string = "";
|
||||
public company: Company = new Company;
|
||||
public trackUUID: string = crypto.randomUUID();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div>
|
||||
@for (application of List; track application.trackUUID){
|
||||
<div>
|
||||
<h1>{{ application.responseEmail }}</h1>
|
||||
<h1>{{ application.notes }}</h1>
|
||||
<h1>{{ application.hasBeenViewed }}</h1>
|
||||
<h1>{{ application.rating }}</h1>
|
||||
<h1>{{ application.responseStatus }}</h1>
|
||||
|
||||
<h1>Date Applied: </h1><h1>{{ application.dateApplied }}</h1>
|
||||
|
||||
<button type="button" (click)="viewResume(application)" >VIEW RESUME</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -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<Application[]>("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');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,6 +46,7 @@
|
||||
<div class="center-text">
|
||||
<h1>{{ listing.title }}</h1>
|
||||
</div>
|
||||
<button [routerLink]="['/application/viewer']" [queryParams]="{ JobID: listing.id }" >VIEW LISTING</button>
|
||||
<button [routerLink]="['/jobs/viewer']" [queryParams]="{ JobID: listing.id }" >VIEW LISTING</button>
|
||||
<button [routerLink]="['/jobs/editor']" [queryParams]="{ JobID: listing.id }" >EDIT LISTING</button>
|
||||
<button (click)="RemoveJobListing(listing.id!)">DELETE LISTING</button>
|
||||
|
||||
@@ -60,11 +60,11 @@
|
||||
<div class="split">
|
||||
<div class="half-frame">
|
||||
<label>Company Email</label>
|
||||
<input class="input-field" name="email" [(ngModel)]="newListing.email" type="text" placeholder="Questions@mistox.com" />
|
||||
<input class="input-field" name="email" [(ngModel)]="newListing.email" type="text" (ngModelChange)="validateEmail(newListing.email)" placeholder="Questions@mistox.com" />
|
||||
</div>
|
||||
<div class="half-frame">
|
||||
<label>Company Phone Number</label>
|
||||
<input class="input-field" name="phone" [(ngModel)]="newListing.phone" type="text" placeholder="+1 800-000-0000" />
|
||||
<input class="input-field" name="phone" [(ngModel)]="newListing.phone" type="tel" (ngModelChange)="validatePhone(newListing.phone)" placeholder="+1 800-000-0000" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" (click)="prevStep()">Back</button>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skills -->
|
||||
<div #step class="sub-frame">
|
||||
<div class="center">
|
||||
<div class="content-frame">
|
||||
<label>Skills</label>
|
||||
<button type="button" (click)="addSkill()">+</button>
|
||||
</div>
|
||||
@for(skill of Listing.skills; track skill.trackUUID){
|
||||
<div class="content-frame">
|
||||
<input [name]="'skillname' + skill.trackUUID" [(ngModel)]="skill.name" type="text" />
|
||||
<textarea [name]="'skilldescription' + skill.trackUUID" [(ngModel)]="skill.description" type="text"></textarea>
|
||||
<button type="button" (click)="remSkill(skill)">-</button>
|
||||
</div>
|
||||
}
|
||||
<div class="content-frame">
|
||||
<button type="button" (click)="prevStep()">Back</button>
|
||||
<button type="button" (click)="nextStep()">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div #step class="sub-frame">
|
||||
<div class="center">
|
||||
|
||||
@@ -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<JobListing>("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<this.Listing.skills.length; i++){
|
||||
let cur = this.Listing.skills[i];
|
||||
if (cur.trackUUID === self.trackUUID){
|
||||
this.Listing.skills.splice( i, 1 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextStep(){
|
||||
this.currentStep += 1;
|
||||
this.updateUI();
|
||||
|
||||
@@ -47,7 +47,24 @@
|
||||
<h1>{{ selectedJob.country }}</h1>
|
||||
<h1>{{ selectedJob.postalCode }}</h1>
|
||||
|
||||
<div>
|
||||
<h1>Required Skills</h1>
|
||||
@for(skill of selectedJob.skills; track skill.trackUUID){
|
||||
<div>
|
||||
<h1>{{ skill.name }}</h1>
|
||||
<h1>{{ skill.description }}</h1>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<h1>{{ selectedJob.description }}</h1>
|
||||
<div>
|
||||
@for(resume of myResumes; track resume.trackUUID){
|
||||
<div>
|
||||
<button type="button" (click)="applyWithResume(resume)">APPLY USING RESUME: {{ resume.name }}</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -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<JobListing>( "api/joblisting/" + JobID ).subscribe({
|
||||
next: data => {
|
||||
this.selectedJob = data;
|
||||
@@ -51,6 +57,41 @@ export class JobViewerComponent {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
this.http.get<Resume[]>("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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,8 +23,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<input [name]="'resumeemail' + resume.trackUUID" [(ngModel)]="resume.email" type="email" placeholder="Email Address" />
|
||||
<input [name]="'resumephoneNumber' + resume.trackUUID" [(ngModel)]="resume.phoneNumber" type="tel" placeholder="Phone number" />
|
||||
<input [name]="'resumeemail' + resume.trackUUID" [(ngModel)]="resume.email" type="email" (ngModelChange)="validateEmail(resume.email)" placeholder="Email Address" />
|
||||
<input [name]="'resumephoneNumber' + resume.trackUUID" [(ngModel)]="resume.phoneNumber" (ngModelChange)="validatePhone(resume.phoneNumber)" type="tel" placeholder="Phone number" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
|
||||
<div class="resume-header">
|
||||
<div>
|
||||
<input [name]="'resumetitle' + resume.trackUUID" [(ngModel)]="resume.title" type="text" placeholder="Resume Name" />
|
||||
</div>
|
||||
<h1>Public: </h1><input [name]="'active' + resume.trackUUID" [(ngModel)]="resume.isActive" type="checkbox" />
|
||||
<h1>Is Veteran: </h1><input [name]="'veteran' + resume.military?.trackUUID" type="checkbox" [checked]="resume.military !== null" />
|
||||
</div>
|
||||
|
||||
<div class="paper">
|
||||
<div class="spacer">
|
||||
<!-- Resume Header -->
|
||||
<div class="resume-section spacer-title">
|
||||
<div class="header-left">
|
||||
<input [name]="'resumename' + resume.trackUUID" [(ngModel)]="resume.name" type="text" placeholder="Full Name" />
|
||||
<input [name]="'resumefield' + resume.trackUUID" [(ngModel)]="resume.field" type="text" placeholder="Career Field" />
|
||||
<div class="header-location">
|
||||
<input [name]="'resumecity' + resume.trackUUID" [(ngModel)]="resume.city" type="text" placeholder="City" />
|
||||
<input [name]="'resumestateOrRegion' + resume.trackUUID" [(ngModel)]="resume.stateOrRegion" type="text" placeholder="State" />
|
||||
<input [name]="'resumecountry' + resume.trackUUID" [(ngModel)]="resume.country" type="text" placeholder="Country" />
|
||||
<input [name]="'resumepostalCode' + resume.trackUUID" [(ngModel)]="resume.postalCode" type="text" placeholder="Postal Code" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<input [name]="'resumeemail' + resume.trackUUID" [(ngModel)]="resume.email" type="email" placeholder="Email Address" />
|
||||
<input [name]="'resumephoneNumber' + resume.trackUUID" [(ngModel)]="resume.phoneNumber" type="tel" placeholder="Phone number" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<!-- Experience -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Experience</h1>
|
||||
@for(experience of resume.experiences; track experience.trackUUID ){
|
||||
<div class="resume-sub-section">
|
||||
<input [name]="'experiencejobTitle' + experience.trackUUID" [(ngModel)]="experience.jobTitle" type="text" placeholder="Job Title" />
|
||||
<input [name]="'experiencecompany' + experience.trackUUID" [(ngModel)]="experience.company" type="text" placeholder="Company" />
|
||||
<input [name]="'experiencecity' + experience.trackUUID" [(ngModel)]="experience.city" type="text" placeholder="City" />
|
||||
<input [name]="'experiencestateOrRegion' + experience.trackUUID" [(ngModel)]="experience.stateOrRegion" type="text" placeholder="State" />
|
||||
<input [name]="'experiencecountry' + experience.trackUUID" [(ngModel)]="experience.country" type="text" placeholder="Country" />
|
||||
<input [name]="'experiencepostalCode' + experience.trackUUID" [(ngModel)]="experience.postalCode" type="text" placeholder="Postal Code" />
|
||||
<input [name]="'experiencestillEmployed' + experience.trackUUID" [(ngModel)]="experience.stillEmployed" type="checkbox" />
|
||||
<input [name]="'experiencedateStarted' + experience.trackUUID" [(ngModel)]="experience.dateStarted" type="date" />
|
||||
@if(!experience.stillEmployed){
|
||||
<input [name]="'experiencedateEnded' + experience.trackUUID" [(ngModel)]="experience.dateEnded" type="date" />
|
||||
}
|
||||
@for(bullet of experience.experienceBullets; track bullet.trackUUID){
|
||||
<div>
|
||||
<textarea [name]="'bulletjobFunction' + bullet.trackUUID" [(ngModel)]="bullet.jobFunction" placeholder="Job Task / Function" ></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Military -->
|
||||
@if(resume.military !== null){
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Military</h1>
|
||||
<input [name]="'militarycountry' + resume.military.trackUUID" [(ngModel)]="resume.military.country" type="text" placeholder="Country" />
|
||||
<input [name]="'militaryrank' + resume.military.trackUUID" [(ngModel)]="resume.military.rank" type="text" placeholder="Rank" />
|
||||
<h1>Still Serving: </h1><input [name]="'stillServing' + resume.military.trackUUID" [(ngModel)]="resume.military.stillServing" type="checkbox" />
|
||||
<input [name]="'militarydateStarted' + resume.military.trackUUID" [(ngModel)]="resume.military.dateStarted" type="date" />
|
||||
@if (!resume.military.stillServing){
|
||||
<input [name]="'dateEnded' + resume.military.trackUUID" [(ngModel)]="resume.military.dateEnded" type="date" />
|
||||
}
|
||||
@for(military of resume.military.militaryBullets; track military.trackUUID ){
|
||||
<div class="resume-sub-section">
|
||||
<input [name]="'militaryachievement' + military.trackUUID" [(ngModel)]="military.achievement" type="text" placeholder="Achievement" />
|
||||
<textarea [name]="'militarydescription' + military.trackUUID" [(ngModel)]="military.description" placeholder="Description" ></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Education -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Education</h1>
|
||||
@for(education of resume.educations; track education.trackUUID){
|
||||
<div class="resume-sub-section">
|
||||
<input [name]="'educationschool' + education.trackUUID" [(ngModel)]="education.school" type="text" placeholder="School" />
|
||||
<input [name]="'educationdegreeType' + education.trackUUID" [(ngModel)]="education.degreeType" type="text" placeholder="Type" />
|
||||
<input [name]="'educationdegreeField' + education.trackUUID" [(ngModel)]="education.degreeField" type="text" placeholder="Field" />
|
||||
<input [name]="'educationcity' + education.trackUUID" [(ngModel)]="education.city" type="text" placeholder="City" />
|
||||
<input [name]="'educationstateOrRegion' + education.trackUUID" [(ngModel)]="education.stateOrRegion" type="text" placeholder="State" />
|
||||
<input [name]="'educationcountry' + education.trackUUID" [(ngModel)]="education.country" type="text" placeholder="Country" />
|
||||
<input [name]="'educationpostalCode' + education.trackUUID" [(ngModel)]="education.postalCode" type="text" placeholder="Postal Code" />
|
||||
<input [name]="'educationstillStudying' + education.trackUUID" [(ngModel)]="education.stillStudying" type="checkbox" />
|
||||
<input [name]="'educationdateStarted' + education.trackUUID" [(ngModel)]="education.dateStarted" type="date" />
|
||||
@if (!education.stillStudying){
|
||||
<input [name]="'educationdateEnded' + education.trackUUID" [(ngModel)]="education.dateEnded" type="date" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="resume-header">
|
||||
<div>
|
||||
<input [name]="'resumetitle' + resume.trackUUID" [(ngModel)]="resume.title" type="text" placeholder="Resume Name" />
|
||||
</div>
|
||||
<h1>Public: </h1><input [name]="'active' + resume.trackUUID" [(ngModel)]="resume.isActive" type="checkbox" />
|
||||
<h1>Is Veteran: </h1><input [name]="'veteran' + resume.military?.trackUUID" type="checkbox" [checked]="resume.military !== null" />
|
||||
</div>
|
||||
|
||||
<div class="paper">
|
||||
<div class="spacer">
|
||||
<!-- Resume Header -->
|
||||
<div class="resume-section spacer-title">
|
||||
<div class="header-left">
|
||||
<input [name]="'resumename' + resume.trackUUID" [(ngModel)]="resume.name" type="text" placeholder="Full Name" />
|
||||
<input [name]="'resumefield' + resume.trackUUID" [(ngModel)]="resume.field" type="text" placeholder="Career Field" />
|
||||
<div class="header-location">
|
||||
<input [name]="'resumecity' + resume.trackUUID" [(ngModel)]="resume.city" type="text" placeholder="City" />
|
||||
<input [name]="'resumestateOrRegion' + resume.trackUUID" [(ngModel)]="resume.stateOrRegion" type="text" placeholder="State" />
|
||||
<input [name]="'resumecountry' + resume.trackUUID" [(ngModel)]="resume.country" type="text" placeholder="Country" />
|
||||
<input [name]="'resumepostalCode' + resume.trackUUID" [(ngModel)]="resume.postalCode" type="text" placeholder="Postal Code" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<input [name]="'resumeemail' + resume.trackUUID" [(ngModel)]="resume.email" type="email" placeholder="Email Address" />
|
||||
<input [name]="'resumephoneNumber' + resume.trackUUID" [(ngModel)]="resume.phoneNumber" type="tel" placeholder="Phone number" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<!-- Experience -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Experience</h1>
|
||||
@for(experience of resume.experiences; track experience.trackUUID ){
|
||||
<div class="resume-sub-section">
|
||||
<input [name]="'experiencejobTitle' + experience.trackUUID" [(ngModel)]="experience.jobTitle" type="text" placeholder="Job Title" />
|
||||
<input [name]="'experiencecompany' + experience.trackUUID" [(ngModel)]="experience.company" type="text" placeholder="Company" />
|
||||
<input [name]="'experiencecity' + experience.trackUUID" [(ngModel)]="experience.city" type="text" placeholder="City" />
|
||||
<input [name]="'experiencestateOrRegion' + experience.trackUUID" [(ngModel)]="experience.stateOrRegion" type="text" placeholder="State" />
|
||||
<input [name]="'experiencecountry' + experience.trackUUID" [(ngModel)]="experience.country" type="text" placeholder="Country" />
|
||||
<input [name]="'experiencepostalCode' + experience.trackUUID" [(ngModel)]="experience.postalCode" type="text" placeholder="Postal Code" />
|
||||
<input [name]="'experiencestillEmployed' + experience.trackUUID" [(ngModel)]="experience.stillEmployed" type="checkbox" />
|
||||
<input [name]="'experiencedateStarted' + experience.trackUUID" [(ngModel)]="experience.dateStarted" type="date" />
|
||||
@if(!experience.stillEmployed){
|
||||
<input [name]="'experiencedateEnded' + experience.trackUUID" [(ngModel)]="experience.dateEnded" type="date" />
|
||||
}
|
||||
@for(bullet of experience.experienceBullets; track bullet.trackUUID){
|
||||
<div>
|
||||
<textarea [name]="'bulletjobFunction' + bullet.trackUUID" [(ngModel)]="bullet.jobFunction" placeholder="Job Task / Function" ></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Military -->
|
||||
@if(resume.military !== null){
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Military</h1>
|
||||
<input [name]="'militarycountry' + resume.military.trackUUID" [(ngModel)]="resume.military.country" type="text" placeholder="Country" />
|
||||
<input [name]="'militaryrank' + resume.military.trackUUID" [(ngModel)]="resume.military.rank" type="text" placeholder="Rank" />
|
||||
<h1>Still Serving: </h1><input [name]="'stillServing' + resume.military.trackUUID" [(ngModel)]="resume.military.stillServing" type="checkbox" />
|
||||
<input [name]="'militarydateStarted' + resume.military.trackUUID" [(ngModel)]="resume.military.dateStarted" type="date" />
|
||||
@if (!resume.military.stillServing){
|
||||
<input [name]="'dateEnded' + resume.military.trackUUID" [(ngModel)]="resume.military.dateEnded" type="date" />
|
||||
}
|
||||
@for(military of resume.military.militaryBullets; track military.trackUUID ){
|
||||
<div class="resume-sub-section">
|
||||
<input [name]="'militaryachievement' + military.trackUUID" [(ngModel)]="military.achievement" type="text" placeholder="Achievement" />
|
||||
<textarea [name]="'militarydescription' + military.trackUUID" [(ngModel)]="military.description" placeholder="Description" ></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Education -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Education</h1>
|
||||
@for(education of resume.educations; track education.trackUUID){
|
||||
<div class="resume-sub-section">
|
||||
<input [name]="'educationschool' + education.trackUUID" [(ngModel)]="education.school" type="text" placeholder="School" />
|
||||
<input [name]="'educationdegreeType' + education.trackUUID" [(ngModel)]="education.degreeType" type="text" placeholder="Type" />
|
||||
<input [name]="'educationdegreeField' + education.trackUUID" [(ngModel)]="education.degreeField" type="text" placeholder="Field" />
|
||||
<input [name]="'educationcity' + education.trackUUID" [(ngModel)]="education.city" type="text" placeholder="City" />
|
||||
<input [name]="'educationstateOrRegion' + education.trackUUID" [(ngModel)]="education.stateOrRegion" type="text" placeholder="State" />
|
||||
<input [name]="'educationcountry' + education.trackUUID" [(ngModel)]="education.country" type="text" placeholder="Country" />
|
||||
<input [name]="'educationpostalCode' + education.trackUUID" [(ngModel)]="education.postalCode" type="text" placeholder="Postal Code" />
|
||||
<input [name]="'educationstillStudying' + education.trackUUID" [(ngModel)]="education.stillStudying" type="checkbox" />
|
||||
<input [name]="'educationdateStarted' + education.trackUUID" [(ngModel)]="education.dateStarted" type="date" />
|
||||
@if (!education.stillStudying){
|
||||
<input [name]="'educationdateEnded' + education.trackUUID" [(ngModel)]="education.dateEnded" type="date" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Skill -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Skills</h1>
|
||||
@for(skill of resume.skills; track skill.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<div>
|
||||
<input [name]="'skillname' + skill.trackUUID" [(ngModel)]="skill.name" type="text" placeholder="Skill" />
|
||||
</div>
|
||||
<textarea [name]="'skilldescription' + skill.trackUUID" [(ngModel)]="skill.description" placeholder="Description"></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Language -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Languages</h1>
|
||||
@for(language of resume.languages; track language.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<input [name]="'languagelanguage' + language.trackUUID" [(ngModel)]="language.language" type="text" placeholder="Language" />
|
||||
<input [name]="'languageproficiency' + language.trackUUID" [(ngModel)]="language.proficiency" type="text" placeholder="Proficiency" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Certification -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Certifications</h1>
|
||||
@for(cert of resume.certifications; track cert.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<div>
|
||||
<input [name]="'certname' + cert.trackUUID" [(ngModel)]="cert.name" type="text" placeholder="Certification Name" />
|
||||
<input [name]="'certverificationURL' + cert.trackUUID" [(ngModel)]="cert.verificationURL" type="text" placeholder="Verification URL" />
|
||||
</div>
|
||||
<textarea [name]="'certdescription' + cert.trackUUID" [(ngModel)]="cert.description" placeholder="Description"></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Project -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Projects</h1>
|
||||
@for(proj of resume.projects; track proj.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<div>
|
||||
<input [name]="'projname' + proj.trackUUID" [(ngModel)]="proj.name" type="text" placeholder="Project Name" />
|
||||
<input [name]="'projurl' + proj.trackUUID" [(ngModel)]="proj.url" type="text" placeholder="Reference URL" />
|
||||
</div>
|
||||
<textarea [name]="'projdescription' + proj.trackUUID" [(ngModel)]="proj.description" placeholder="Description"></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Skill -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Skills</h1>
|
||||
@for(skill of resume.skills; track skill.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<div>
|
||||
<input [name]="'skillname' + skill.trackUUID" [(ngModel)]="skill.name" type="text" placeholder="Skill" />
|
||||
</div>
|
||||
<textarea [name]="'skilldescription' + skill.trackUUID" [(ngModel)]="skill.description" placeholder="Description"></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Language -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Languages</h1>
|
||||
@for(language of resume.languages; track language.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<input [name]="'languagelanguage' + language.trackUUID" [(ngModel)]="language.language" type="text" placeholder="Language" />
|
||||
<input [name]="'languageproficiency' + language.trackUUID" [(ngModel)]="language.proficiency" type="text" placeholder="Proficiency" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Certification -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Certifications</h1>
|
||||
@for(cert of resume.certifications; track cert.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<div>
|
||||
<input [name]="'certname' + cert.trackUUID" [(ngModel)]="cert.name" type="text" placeholder="Certification Name" />
|
||||
<input [name]="'certverificationURL' + cert.trackUUID" [(ngModel)]="cert.verificationURL" type="text" placeholder="Verification URL" />
|
||||
</div>
|
||||
<textarea [name]="'certdescription' + cert.trackUUID" [(ngModel)]="cert.description" placeholder="Description"></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Project -->
|
||||
<div class="resume-section">
|
||||
<h1 class="title-text">Projects</h1>
|
||||
@for(proj of resume.projects; track proj.trackUUID){
|
||||
<div class="resume-sub-section flex-two-row">
|
||||
<div>
|
||||
<input [name]="'projname' + proj.trackUUID" [(ngModel)]="proj.name" type="text" placeholder="Project Name" />
|
||||
<input [name]="'projurl' + proj.trackUUID" [(ngModel)]="proj.url" type="text" placeholder="Reference URL" />
|
||||
</div>
|
||||
<textarea [name]="'projdescription' + proj.trackUUID" [(ngModel)]="proj.description" placeholder="Description"></textarea>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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<Resume>("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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,13 +10,19 @@ namespace BoredCareers.Controllers {
|
||||
public ApplicationController(DatabaseService db) : base(db) {}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetApplication(int ApplicationID) {
|
||||
public async Task<IActionResult> 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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user