Added the missing integration to dataservice to replace hardcoded data in components

This commit is contained in:
AdamBtech
2025-06-02 22:36:57 +02:00
parent 0d2a653d71
commit 3e9b92d3d2
13 changed files with 279 additions and 201 deletions

View File

@@ -1,4 +1,5 @@
{
{
"id": "developer-training",
"title": "DEVELOPER_TRAINING.EXE",
"classification": "Concepteur Développeur d'Applications (RNCP Level BAC+4)",
@@ -13,4 +14,5 @@
"status": "current",
"statusLabel": "ACTIVE | IN_PROGRESS",
"isCurrent": true
}
}

View File

@@ -2,6 +2,9 @@
<div class="column education-column">
<h2 class="column-title">[EDUCATION_PROTOCOL]</h2>
@if (dataService.educations().length === 0) {
<div class="loading">Loading education data...</div>
} @else {
@if (currentTraining(); as training) {
<article class="experience-card active-training">
<div class="corner-accent"></div>
@@ -15,9 +18,13 @@
<div class="experience-body">
<app-detail-block label="PROGRAM:" [value]="training.classification" />
<app-detail-block label="INSTITUTION:" [value]="training.timeline" />
<app-detail-block label="OBJECTIVE:" [value]="training.objective" />
<app-tech-stack label="CURRICULUM:" [techStack]="training.techStack"/>
</div>
</article>
} @else {
<div class="no-data">No current training found</div>
}
}
<!-- Additional Education Info -->

View File

@@ -1,8 +1,8 @@
import { Component, signal } from '@angular/core';
import { type Experience } from '../../../../shared/models/experience.model';
import { Component, inject, computed } from '@angular/core';
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
import { InfoCardComponent } from '../shared/info-card/info-card.component';
import { TechStackComponent } from '../tech-stack/tech-stack.component';
import { DataService } from '../../../../shared/services/data.service';
@Component({
selector: 'app-column-education-info',
@@ -11,23 +11,22 @@ import { TechStackComponent } from '../tech-stack/tech-stack.component';
styleUrl: './column-education-info.component.css',
})
export class ColumnEducationInfoComponent {
private _currentTraining = signal<Experience>({
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);
readonly dataService = inject(DataService);
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;
});
}

View File

@@ -38,8 +38,6 @@
animation: slideInLeft 0.4s ease-out calc(var(--animation-delay-base) * 7) forwards;
}
/* Personal Info Grid */
.trait-grid {
display: grid;
@@ -47,32 +45,6 @@
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 {
margin-bottom: 0.5rem;
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 {
from {
opacity: 0;
@@ -252,18 +213,6 @@
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 {
margin-bottom: 0.75rem;
padding: 0.5rem 0.25rem;
@@ -323,7 +272,6 @@
/* Remove complex animations on very small screens */
@media (max-width: 360px) {
.trait-item,
.interest-item,
.philosophy-text,
.philosophy-text::before,
@@ -346,7 +294,6 @@
.personal-column,
.column-title,
.info-card,
.trait-item,
.interest-item,
.philosophy-text,
.philosophy-text::before,

View File

@@ -17,14 +17,15 @@
</app-info-card>
<app-info-card headerTitle="CORE_TRAITS">
@if (dataService.traits().length === 0) {
<div class="loading">Loading traits...</div>
} @else {
<div class="trait-grid">
<span class="trait-item">CURIOUS</span>
<span class="trait-item">PERSISTENT</span>
<span class="trait-item">ANALYTICAL</span>
<span class="trait-item">CREATIVE</span>
<span class="trait-item">ADAPTIVE</span>
<span class="trait-item">SELF-LEARNER</span>
@for (trait of traits(); track trait) {
<app-trait-item [traitvalue]="trait"/>
}
</div>
}
</app-info-card>
<app-info-card headerTitle="PHILOSOPHY">
@@ -35,7 +36,4 @@
{{ personalNote() }}
</div>
</app-info-card>
</div>

View File

@@ -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 { TraitItemComponent } from './trait-item/trait-item.component';
import { DataService } from '../../../../shared/services/data.service';
@Component({
selector: 'app-column-personal-info',
imports: [InfoCardComponent],
imports: [InfoCardComponent, TraitItemComponent],
templateUrl: './column-personal-info.component.html',
styleUrl: './column-personal-info.component.css',
})
export class ColumnPersonalInfoComponent {
readonly dataService = inject(DataService);
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.',
);
@@ -20,4 +24,7 @@ export class ColumnPersonalInfoComponent {
readonly personalNote = this._personalNote.asReadonly();
readonly philosophyText = this._philosophyText.asReadonly();
// Use traits from DataService
traits = this.dataService.traits;
}

View File

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

View File

@@ -0,0 +1,2 @@
<!-- trait-item.component.html -->
<span class="trait-item">{{ traitvalue() }}</span>

View File

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

View File

@@ -1,6 +1,9 @@
<div class="column professional-column">
<h2 class="column-title">[PROFESSIONAL_ARCHIVE]</h2>
@if (dataService.experiences().length === 0) {
<div class="loading">Loading professional experience...</div>
} @else {
<div class="experience-list">
@for (experience of experiences(); track experience.id) {
<article class="experience-card">
@@ -29,4 +32,5 @@
</article>
}
</div>
}
</div>

View File

@@ -1,7 +1,7 @@
import { Component, signal } from '@angular/core';
import { Experience } from '../../../../shared/models/experience.model';
import { Component, inject } from '@angular/core';
import { DetailBlockComponent } from '../shared/detail-block/detail-block.component';
import { TechStackComponent } from '../tech-stack/tech-stack.component';
import { DataService } from '../../../../shared/services/data.service';
@Component({
selector: 'app-column-professional-info',
@@ -10,52 +10,8 @@ import { TechStackComponent } from '../tech-stack/tech-stack.component';
styleUrl: './column-professional-info.component.css',
})
export class ColumnProfessionalInfoComponent {
private _experiences = signal<Experience[]>([
{
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',
},
]);
readonly dataService = inject(DataService);
experiences = this._experiences;
// Use experiences from DataService
experiences = this.dataService.experiences;
}

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

View File

@@ -2,7 +2,9 @@ import { Injectable, signal, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { type Project } from '../models/project.model';
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({
providedIn: 'root',
@@ -13,10 +15,16 @@ export class DataService {
// Private signals for state management
private _projects = signal<Project[]>([]);
private _skills = signal<NeuralProfileNode[]>([]);
private _educations = signal<Experience[]>([]);
private _experiences = signal<Experience[]>([]);
private _traits = signal<string[]>([]);
// Public readonly signals
readonly projects = this._projects.asReadonly();
readonly skills = this._skills.asReadonly();
readonly educations = this._educations.asReadonly();
readonly experiences = this._experiences.asReadonly();
readonly traits = this._traits.asReadonly();
constructor() {
this.loadInitialData();
@@ -24,27 +32,93 @@ export class DataService {
private async loadInitialData(): Promise<void> {
try {
const [projects, skills] = await Promise.all([
const [projects, skills, educations, experiences, traits] =
await Promise.all([
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._skills.set(skills);
this._educations.set(educations);
this._experiences.set(experiences);
this._traits.set(traits);
} catch (error) {
console.error('Error loading data:', error);
}
}
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[]> {
try {
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 [];
}
}
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 [];
}
}
}