mirror of
https://github.com/adam-benyekkou/my_portfolio.git
synced 2026-01-15 20:20:09 +00:00
Added the missing integration to dataservice to replace hardcoded data in components
This commit is contained in:
@@ -1,16 +1,18 @@
|
|||||||
{
|
|
||||||
"id": "developer-training",
|
{
|
||||||
"title": "DEVELOPER_TRAINING.EXE",
|
"id": "developer-training",
|
||||||
"classification": "Concepteur Développeur d'Applications (RNCP Level BAC+4)",
|
"title": "DEVELOPER_TRAINING.EXE",
|
||||||
"objective": "Intensive full-stack development program focusing on modern web technologies including TypeScript, Node.js, and contemporary development frameworks. Building comprehensive skills in both frontend and backend development.",
|
"classification": "Concepteur Développeur d'Applications (RNCP Level BAC+4)",
|
||||||
"timeline": "École O'clock | January 2025 - October 2025",
|
"objective": "Intensive full-stack development program focusing on modern web technologies including TypeScript, Node.js, and contemporary development frameworks. Building comprehensive skills in both frontend and backend development.",
|
||||||
"techStack": [
|
"timeline": "École O'clock | January 2025 - October 2025",
|
||||||
"TYPESCRIPT",
|
"techStack": [
|
||||||
"NODE.JS",
|
"TYPESCRIPT",
|
||||||
"FULL_STACK",
|
"NODE.JS",
|
||||||
"WEB_FRAMEWORKS"
|
"FULL_STACK",
|
||||||
],
|
"WEB_FRAMEWORKS"
|
||||||
"status": "current",
|
],
|
||||||
"statusLabel": "ACTIVE | IN_PROGRESS",
|
"status": "current",
|
||||||
"isCurrent": true
|
"statusLabel": "ACTIVE | IN_PROGRESS",
|
||||||
}
|
"isCurrent": true
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,22 +2,29 @@
|
|||||||
<div class="column education-column">
|
<div class="column education-column">
|
||||||
<h2 class="column-title">[EDUCATION_PROTOCOL]</h2>
|
<h2 class="column-title">[EDUCATION_PROTOCOL]</h2>
|
||||||
|
|
||||||
@if (currentTraining(); as training) {
|
@if (dataService.educations().length === 0) {
|
||||||
<article class="experience-card active-training">
|
<div class="loading">Loading education data...</div>
|
||||||
<div class="corner-accent"></div>
|
} @else {
|
||||||
<div class="scan-line"></div>
|
@if (currentTraining(); as training) {
|
||||||
|
<article class="experience-card active-training">
|
||||||
|
<div class="corner-accent"></div>
|
||||||
|
<div class="scan-line"></div>
|
||||||
|
|
||||||
<header class="experience-header">
|
<header class="experience-header">
|
||||||
<div class="current-status">{{ training.statusLabel }}</div>
|
<div class="current-status">{{ training.statusLabel }}</div>
|
||||||
<h3 class="experience-title">{{ training.title }}</h3>
|
<h3 class="experience-title">{{ training.title }}</h3>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="experience-body">
|
<div class="experience-body">
|
||||||
<app-detail-block label="PROGRAM:" [value]="training.classification" />
|
<app-detail-block label="PROGRAM:" [value]="training.classification" />
|
||||||
<app-detail-block label="INSTITUTION:" [value]="training.timeline" />
|
<app-detail-block label="INSTITUTION:" [value]="training.timeline" />
|
||||||
<app-tech-stack label="CURRICULUM:" [techStack]="training.techStack"/>
|
<app-detail-block label="OBJECTIVE:" [value]="training.objective" />
|
||||||
</div>
|
<app-tech-stack label="CURRICULUM:" [techStack]="training.techStack"/>
|
||||||
</article>
|
</div>
|
||||||
|
</article>
|
||||||
|
} @else {
|
||||||
|
<div class="no-data">No current training found</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Additional Education Info -->
|
<!-- Additional Education Info -->
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component, inject, computed } from '@angular/core';
|
||||||
import { type Experience } from '../../../../shared/models/experience.model';
|
|
||||||
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
||||||
import { InfoCardComponent } from '../shared/info-card/info-card.component';
|
import { InfoCardComponent } from '../shared/info-card/info-card.component';
|
||||||
import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
||||||
|
import { DataService } from '../../../../shared/services/data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-column-education-info',
|
selector: 'app-column-education-info',
|
||||||
@@ -11,23 +11,22 @@ import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
|||||||
styleUrl: './column-education-info.component.css',
|
styleUrl: './column-education-info.component.css',
|
||||||
})
|
})
|
||||||
export class ColumnEducationInfoComponent {
|
export class ColumnEducationInfoComponent {
|
||||||
private _currentTraining = signal<Experience>({
|
readonly dataService = inject(DataService);
|
||||||
id: 'developer-training',
|
|
||||||
title: 'DEVELOPER_TRAINING.EXE',
|
|
||||||
classification: "Concepteur Développeur d'Applications (RNCP Level BAC+4)",
|
|
||||||
objective:
|
|
||||||
'Intensive full-stack development program focusing on modern web technologies including TypeScript, Node.js, and contemporary development frameworks. Building comprehensive skills in both frontend and backend development.',
|
|
||||||
timeline: "École O'clock | January 2025 - October 2025",
|
|
||||||
techStack: [
|
|
||||||
'TYPESCRIPT',
|
|
||||||
'NODE.JS',
|
|
||||||
'FULL_STACK',
|
|
||||||
'WEB_FRAMEWORKS',
|
|
||||||
] as const,
|
|
||||||
status: 'current',
|
|
||||||
statusLabel: 'ACTIVE | IN_PROGRESS',
|
|
||||||
isCurrent: true,
|
|
||||||
} as const);
|
|
||||||
|
|
||||||
currentTraining = this._currentTraining.asReadonly();
|
// Find the current training (status: 'current' or isCurrent: true)
|
||||||
|
currentTraining = computed(() => {
|
||||||
|
const educations = this.dataService.educations();
|
||||||
|
console.log('Educations:', educations);
|
||||||
|
|
||||||
|
if (educations.length === 0) {
|
||||||
|
console.log('No educations loaded yet');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = educations.find(
|
||||||
|
(edu) => edu.status === 'current' || edu.isCurrent,
|
||||||
|
);
|
||||||
|
console.log('Current training:', current);
|
||||||
|
return current || null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,6 @@
|
|||||||
animation: slideInLeft 0.4s ease-out calc(var(--animation-delay-base) * 7) forwards;
|
animation: slideInLeft 0.4s ease-out calc(var(--animation-delay-base) * 7) forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Personal Info Grid */
|
/* Personal Info Grid */
|
||||||
.trait-grid {
|
.trait-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -47,32 +45,6 @@
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trait-item {
|
|
||||||
background: linear-gradient(135deg, var(--color-nier-accent) 0%, var(--color-nier-highlight) 100%);
|
|
||||||
color: var(--color-nier-text-light);
|
|
||||||
padding: 0.375rem 0.25rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-family: var(--font-terminal);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
border-radius: 3px;
|
|
||||||
white-space: nowrap;
|
|
||||||
transition: var(--transition-smooth);
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
/* Initial state for staggered animation */
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.8) translateY(10px);
|
|
||||||
animation: techItemFadeIn 0.4s ease-out forwards;
|
|
||||||
animation-delay: calc(var(--animation-delay-base) * 19);
|
|
||||||
}
|
|
||||||
|
|
||||||
.trait-item:hover {
|
|
||||||
transform: translateY(-2px) scale(1.05);
|
|
||||||
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.interest-item {
|
.interest-item {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -187,17 +159,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes techItemFadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.8) translateY(10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1) translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -252,18 +213,6 @@
|
|||||||
gap: 0.375rem;
|
gap: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trait-item {
|
|
||||||
padding: 0.5rem 0.375rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
|
|
||||||
/* Simplified trait animation */
|
|
||||||
animation-name: techItemFadeIn;
|
|
||||||
animation-duration: 0.2s;
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
animation-delay: 0.7s;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.interest-item {
|
.interest-item {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
padding: 0.5rem 0.25rem;
|
padding: 0.5rem 0.25rem;
|
||||||
@@ -323,7 +272,6 @@
|
|||||||
|
|
||||||
/* Remove complex animations on very small screens */
|
/* Remove complex animations on very small screens */
|
||||||
@media (max-width: 360px) {
|
@media (max-width: 360px) {
|
||||||
.trait-item,
|
|
||||||
.interest-item,
|
.interest-item,
|
||||||
.philosophy-text,
|
.philosophy-text,
|
||||||
.philosophy-text::before,
|
.philosophy-text::before,
|
||||||
@@ -346,7 +294,6 @@
|
|||||||
.personal-column,
|
.personal-column,
|
||||||
.column-title,
|
.column-title,
|
||||||
.info-card,
|
.info-card,
|
||||||
.trait-item,
|
|
||||||
.interest-item,
|
.interest-item,
|
||||||
.philosophy-text,
|
.philosophy-text,
|
||||||
.philosophy-text::before,
|
.philosophy-text::before,
|
||||||
|
|||||||
@@ -17,14 +17,15 @@
|
|||||||
</app-info-card>
|
</app-info-card>
|
||||||
|
|
||||||
<app-info-card headerTitle="CORE_TRAITS">
|
<app-info-card headerTitle="CORE_TRAITS">
|
||||||
<div class="trait-grid">
|
@if (dataService.traits().length === 0) {
|
||||||
<span class="trait-item">CURIOUS</span>
|
<div class="loading">Loading traits...</div>
|
||||||
<span class="trait-item">PERSISTENT</span>
|
} @else {
|
||||||
<span class="trait-item">ANALYTICAL</span>
|
<div class="trait-grid">
|
||||||
<span class="trait-item">CREATIVE</span>
|
@for (trait of traits(); track trait) {
|
||||||
<span class="trait-item">ADAPTIVE</span>
|
<app-trait-item [traitvalue]="trait"/>
|
||||||
<span class="trait-item">SELF-LEARNER</span>
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</app-info-card>
|
</app-info-card>
|
||||||
|
|
||||||
<app-info-card headerTitle="PHILOSOPHY">
|
<app-info-card headerTitle="PHILOSOPHY">
|
||||||
@@ -35,7 +36,4 @@
|
|||||||
{{ personalNote() }}
|
{{ personalNote() }}
|
||||||
</div>
|
</div>
|
||||||
</app-info-card>
|
</app-info-card>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component, signal, inject } from '@angular/core';
|
||||||
import { InfoCardComponent } from '../shared/info-card/info-card.component';
|
import { InfoCardComponent } from '../shared/info-card/info-card.component';
|
||||||
|
import { TraitItemComponent } from './trait-item/trait-item.component';
|
||||||
|
import { DataService } from '../../../../shared/services/data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-column-personal-info',
|
selector: 'app-column-personal-info',
|
||||||
imports: [InfoCardComponent],
|
imports: [InfoCardComponent, TraitItemComponent],
|
||||||
templateUrl: './column-personal-info.component.html',
|
templateUrl: './column-personal-info.component.html',
|
||||||
styleUrl: './column-personal-info.component.css',
|
styleUrl: './column-personal-info.component.css',
|
||||||
})
|
})
|
||||||
export class ColumnPersonalInfoComponent {
|
export class ColumnPersonalInfoComponent {
|
||||||
|
readonly dataService = inject(DataService);
|
||||||
|
|
||||||
private readonly _personalNote = signal<string>(
|
private readonly _personalNote = signal<string>(
|
||||||
'In the spaces between keystrokes and compiler runs, I contemplate the philosophical implications of our digital convergence. When not debugging the matrix, I tend to six small organic beings who remind me that even in our increasingly automated world, care and attention to living creatures remains profoundly important.',
|
'In the spaces between keystrokes and compiler runs, I contemplate the philosophical implications of our digital convergence. When not debugging the matrix, I tend to six small organic beings who remind me that even in our increasingly automated world, care and attention to living creatures remains profoundly important.',
|
||||||
);
|
);
|
||||||
@@ -20,4 +24,7 @@ export class ColumnPersonalInfoComponent {
|
|||||||
|
|
||||||
readonly personalNote = this._personalNote.asReadonly();
|
readonly personalNote = this._personalNote.asReadonly();
|
||||||
readonly philosophyText = this._philosophyText.asReadonly();
|
readonly philosophyText = this._philosophyText.asReadonly();
|
||||||
|
|
||||||
|
// Use traits from DataService
|
||||||
|
traits = this.dataService.traits;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
.trait-item {
|
||||||
|
background: linear-gradient(135deg, var(--color-nier-accent) 0%, var(--color-nier-highlight) 100%);
|
||||||
|
color: var(--color-nier-text-light);
|
||||||
|
padding: 0.375rem 0.25rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-family: var(--font-terminal);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
/* Animation */
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(10px);
|
||||||
|
animation: techItemFadeIn 0.4s ease-out forwards;
|
||||||
|
animation-delay: calc(var(--animation-delay-base, 0.1s) * 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trait-item:hover {
|
||||||
|
transform: translateY(-2px) scale(1.05);
|
||||||
|
box-shadow: 0 6px 12px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation keyframe */
|
||||||
|
@keyframes techItemFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.trait-item {
|
||||||
|
padding: 0.5rem 0.375rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
animation-delay: 0.7s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduced motion support */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.trait-item {
|
||||||
|
animation: none !important;
|
||||||
|
transition: none !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<!-- trait-item.component.html -->
|
||||||
|
<span class="trait-item">{{ traitvalue() }}</span>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component, input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-trait-item',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './trait-item.component.html',
|
||||||
|
styleUrl: './trait-item.component.css',
|
||||||
|
})
|
||||||
|
export class TraitItemComponent {
|
||||||
|
traitvalue = input<string>();
|
||||||
|
}
|
||||||
@@ -1,32 +1,36 @@
|
|||||||
<div class="column professional-column">
|
<div class="column professional-column">
|
||||||
<h2 class="column-title">[PROFESSIONAL_ARCHIVE]</h2>
|
<h2 class="column-title">[PROFESSIONAL_ARCHIVE]</h2>
|
||||||
|
|
||||||
<div class="experience-list">
|
@if (dataService.experiences().length === 0) {
|
||||||
@for (experience of experiences(); track experience.id) {
|
<div class="loading">Loading professional experience...</div>
|
||||||
<article class="experience-card">
|
} @else {
|
||||||
<div class="corner-accent"></div>
|
<div class="experience-list">
|
||||||
@if (experience.status === 'current') {
|
@for (experience of experiences(); track experience.id) {
|
||||||
<div class="scan-line"></div>
|
<article class="experience-card">
|
||||||
}
|
<div class="corner-accent"></div>
|
||||||
|
@if (experience.status === 'current') {
|
||||||
|
<div class="scan-line"></div>
|
||||||
|
}
|
||||||
|
|
||||||
<header class="experience-header">
|
<header class="experience-header">
|
||||||
<div
|
<div
|
||||||
class="status-badge"
|
class="status-badge"
|
||||||
[class.current-status]="experience.status === 'current'"
|
[class.current-status]="experience.status === 'current'"
|
||||||
[class.completed-status]="experience.status === 'completed'">
|
[class.completed-status]="experience.status === 'completed'">
|
||||||
{{ experience.statusLabel }}
|
{{ experience.statusLabel }}
|
||||||
|
</div>
|
||||||
|
<h3 class="experience-title">
|
||||||
|
{{ experience.title }}
|
||||||
|
</h3>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="experience-body">
|
||||||
|
<app-detail-block label="CLASSIFICATION:" [value]="experience.classification"/>
|
||||||
|
<app-detail-block label="TIMELINE:" [value]="experience.timeline"/>
|
||||||
|
<app-tech-stack label="TECH_STACK:" [techStack]="experience.techStack"/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="experience-title">
|
</article>
|
||||||
{{ experience.title }}
|
}
|
||||||
</h3>
|
</div>
|
||||||
</header>
|
}
|
||||||
|
|
||||||
<div class="experience-body">
|
|
||||||
<app-detail-block label="CLASSIFICATION:" [value]="experience.classification"/>
|
|
||||||
<app-detail-block label="TIMELINE:" [value]="experience.timeline"/>
|
|
||||||
<app-tech-stack label="TECH_STACK:" [techStack]="experience.techStack"/>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { Experience } from '../../../../shared/models/experience.model';
|
|
||||||
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
|
||||||
import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
||||||
|
import { DataService } from '../../../../shared/services/data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-column-professional-info',
|
selector: 'app-column-professional-info',
|
||||||
@@ -10,52 +10,8 @@ import { TechStackComponent } from '../tech-stack/tech-stack.component';
|
|||||||
styleUrl: './column-professional-info.component.css',
|
styleUrl: './column-professional-info.component.css',
|
||||||
})
|
})
|
||||||
export class ColumnProfessionalInfoComponent {
|
export class ColumnProfessionalInfoComponent {
|
||||||
private _experiences = signal<Experience[]>([
|
readonly dataService = inject(DataService);
|
||||||
{
|
|
||||||
id: 'senior-specialist',
|
|
||||||
title: 'PYTHON_API_SPECIALIST.EXE',
|
|
||||||
classification: 'Python & Django API/RPA Specialist',
|
|
||||||
objective:
|
|
||||||
'Specialized in Python/Django development for API automation and RPA processes. Led technical interventions for complex enterprise HR system integrations, developing robust solutions that bridge business requirements with technical implementation.',
|
|
||||||
timeline: 'UKG | January 2024 - January 2025',
|
|
||||||
techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'],
|
|
||||||
status: 'completed',
|
|
||||||
statusLabel: 'ARCHIVED | SME_SPECIALIST',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'specialist-ii',
|
|
||||||
title: 'SYSTEMS_INTEGRATION_SME.EXE',
|
|
||||||
classification: 'Enterprise Systems Integration',
|
|
||||||
objective:
|
|
||||||
'Subject Matter Expert (SME) for enterprise HRSD connectors and system integrations. Specialized in complex technical challenges involving API development, RPA automation, and SFTP protocols. Provided BI support and data visualization solutions.',
|
|
||||||
timeline: 'UKG | January 2023 - January 2024',
|
|
||||||
techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'],
|
|
||||||
status: 'completed',
|
|
||||||
statusLabel: 'ARCHIVED | SME_SPECIALIST',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'specialist',
|
|
||||||
title: 'SOLUTION_SUPPORT.EXE',
|
|
||||||
classification: 'B2B Solution Support',
|
|
||||||
objective:
|
|
||||||
'Delivered high-level technical support for VIP business clients using HRSD solutions. Developed expertise in SaaS platforms and enterprise decision-making processes while managing critical client relationships.',
|
|
||||||
timeline: 'UKG | October 2021 - January 2023',
|
|
||||||
techStack: ['SAAS', 'HRSD', 'CLIENT_MGMT'],
|
|
||||||
status: 'completed',
|
|
||||||
statusLabel: 'ARCHIVED | VIP_TIER',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'technician',
|
|
||||||
title: 'IT_TECHNICIAN.EXE',
|
|
||||||
classification: 'Information Technology Technician',
|
|
||||||
objective:
|
|
||||||
'Provided VIP end-user support, managed hardware/software deployments, and implemented networking and information security practices in a mission-critical environment.',
|
|
||||||
timeline: 'Armée de Terre | July 2018 - December 2018',
|
|
||||||
techStack: ['NETWORKING', 'SECURITY', 'DEPLOYMENT'],
|
|
||||||
status: 'completed',
|
|
||||||
statusLabel: 'ARCHIVED | MILITARY',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
experiences = this._experiences;
|
// Use experiences from DataService
|
||||||
|
experiences = this.dataService.experiences;
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/app/shared/models/education.model.ts
Normal file
13
src/app/shared/models/education.model.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
type Education = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
classification: string;
|
||||||
|
objective: string;
|
||||||
|
timeline: string;
|
||||||
|
techStack: string[];
|
||||||
|
status: 'current' | 'completed';
|
||||||
|
statusLabel: string;
|
||||||
|
isCurrent?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { type Education };
|
||||||
@@ -2,7 +2,9 @@ import { Injectable, signal, inject } from '@angular/core';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { type Project } from '../models/project.model';
|
import { type Project } from '../models/project.model';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { NeuralProfileNode } from '../models/about.model';
|
import { type NeuralProfileNode } from '../models/about.model';
|
||||||
|
import { type Education } from '../models/education.model';
|
||||||
|
import { type Experience } from '../models/experience.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -13,10 +15,16 @@ export class DataService {
|
|||||||
// Private signals for state management
|
// Private signals for state management
|
||||||
private _projects = signal<Project[]>([]);
|
private _projects = signal<Project[]>([]);
|
||||||
private _skills = signal<NeuralProfileNode[]>([]);
|
private _skills = signal<NeuralProfileNode[]>([]);
|
||||||
|
private _educations = signal<Experience[]>([]);
|
||||||
|
private _experiences = signal<Experience[]>([]);
|
||||||
|
private _traits = signal<string[]>([]);
|
||||||
|
|
||||||
// Public readonly signals
|
// Public readonly signals
|
||||||
readonly projects = this._projects.asReadonly();
|
readonly projects = this._projects.asReadonly();
|
||||||
readonly skills = this._skills.asReadonly();
|
readonly skills = this._skills.asReadonly();
|
||||||
|
readonly educations = this._educations.asReadonly();
|
||||||
|
readonly experiences = this._experiences.asReadonly();
|
||||||
|
readonly traits = this._traits.asReadonly();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.loadInitialData();
|
this.loadInitialData();
|
||||||
@@ -24,27 +32,93 @@ export class DataService {
|
|||||||
|
|
||||||
private async loadInitialData(): Promise<void> {
|
private async loadInitialData(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const [projects, skills] = await Promise.all([
|
const [projects, skills, educations, experiences, traits] =
|
||||||
this.fetchProjects(),
|
await Promise.all([
|
||||||
this.fetchSkills(),
|
this.fetchProjects(),
|
||||||
]);
|
this.fetchSkills(),
|
||||||
|
this.fetchEducations(),
|
||||||
|
this.fetchExperiences(),
|
||||||
|
this.fetchTraits(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log('All data loaded:', {
|
||||||
|
projects,
|
||||||
|
skills,
|
||||||
|
educations,
|
||||||
|
experiences,
|
||||||
|
traits,
|
||||||
|
});
|
||||||
|
|
||||||
this._projects.set(projects);
|
this._projects.set(projects);
|
||||||
this._skills.set(skills);
|
this._skills.set(skills);
|
||||||
|
this._educations.set(educations);
|
||||||
|
this._experiences.set(experiences);
|
||||||
|
this._traits.set(traits);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Error loading data:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchProjects(): Promise<Project[]> {
|
private async fetchProjects(): Promise<Project[]> {
|
||||||
return firstValueFrom(this.http.get<Project[]>('data/projects.json'));
|
try {
|
||||||
|
return await firstValueFrom(
|
||||||
|
this.http.get<Project[]>('data/projects.json'),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching projects:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchSkills(): Promise<NeuralProfileNode[]> {
|
private async fetchSkills(): Promise<NeuralProfileNode[]> {
|
||||||
const result = await firstValueFrom(
|
try {
|
||||||
this.http.get<NeuralProfileNode>('data/skilltree.json'),
|
const result = await firstValueFrom(
|
||||||
);
|
this.http.get<NeuralProfileNode>('data/skilltree.json'),
|
||||||
|
);
|
||||||
|
console.log('Fetched skills:', result);
|
||||||
|
return [result];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching skills:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [result];
|
private async fetchEducations(): Promise<Experience[]> {
|
||||||
|
try {
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
this.http.get<Experience>('data/education.json'),
|
||||||
|
);
|
||||||
|
console.log('Fetched education:', result);
|
||||||
|
return [result];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching education:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchExperiences(): Promise<Experience[]> {
|
||||||
|
try {
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
this.http.get<Experience[]>('data/experiences.json'),
|
||||||
|
);
|
||||||
|
console.log('Fetched experiences:', result);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching experiences:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchTraits(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
this.http.get<string[]>('data/traits.json'),
|
||||||
|
);
|
||||||
|
console.log('Fetched traits:', result);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching traits:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user