working #36

Merged
derek merged 9 commits from working into main 2025-08-26 17:31:04 -07:00
26 changed files with 931 additions and 23 deletions
+11 -1
View File
@@ -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
+6
View File
@@ -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 },
-1
View File
@@ -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() => {
+1
View File
@@ -9,4 +9,5 @@ export class Application {
public hasBeenViewed: boolean = false;
public rating: number = 0;
public notes: string = "";
public trackUUID: string = crypto.randomUUID();
}
+1
View File
@@ -13,4 +13,5 @@ export class Company {
public stateOrRegion: string = "";
public city: string = "";
public description: string = "";
public trackUUID: string = crypto.randomUUID();
}
+1
View File
@@ -6,4 +6,5 @@ export class Employee {
public accountName: string = "";
public accountEmail: string = "";
public company: Company = new Company;
public trackUUID: string = crypto.randomUUID();
}
+3 -1
View File
@@ -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;
}
}
+35
View File
@@ -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)