From c1d05c005e9a9497560a96f76e959c788288af21 Mon Sep 17 00:00:00 2001 From: AdamBtech <60339324+AdamBtech@users.noreply.github.com> Date: Mon, 2 Jun 2025 19:42:38 +0200 Subject: [PATCH] Refactoring tech stack into a reusable component --- .../column-education-info.component.css | 83 ------------------- .../column-education-info.component.html | 10 +-- .../column-education-info.component.ts | 7 +- .../column-professional-info.component.css | 58 ------------- .../column-professional-info.component.html | 10 +-- .../column-professional-info.component.ts | 17 ++-- .../tech-stack/tech-stack.component.css | 61 ++++++++++++++ .../tech-stack/tech-stack.component.html | 9 ++ .../tech-stack/tech-stack.component.ts | 12 +++ src/app/shared/models/experience.model.ts | 18 ++-- 10 files changed, 106 insertions(+), 179 deletions(-) create mode 100644 src/app/pages/experience/operative-history/tech-stack/tech-stack.component.css create mode 100644 src/app/pages/experience/operative-history/tech-stack/tech-stack.component.html create mode 100644 src/app/pages/experience/operative-history/tech-stack/tech-stack.component.ts diff --git a/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.css b/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.css index 3338ef6..e9f2f6a 100644 --- a/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.css +++ b/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.css @@ -125,65 +125,6 @@ text-shadow: 0 1px 2px rgba(0,0,0,0.1); } -/* Tech Stack Section */ -.tech-section { - margin-top: var(--card-gap); - padding-top: var(--card-gap); - border-top: 1px solid var(--color-nier-border); - position: relative; -} - -.tech-section::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 0; - height: 1px; - background: var(--color-nier-accent); - animation: expandLine 0.6s ease-out calc(var(--animation-delay-base) * 12) forwards; -} - -.tech-grid { - display: flex; - flex-wrap: wrap; - gap: 0.25rem; - margin-top: 0.5rem; -} - -/* Tech Items */ -.tech-item { - background: linear-gradient(135deg, var(--color-nier-accent) 0%, var(--color-nier-highlight) 100%); - color: var(--color-nier-text-light); - padding: 0.25rem 0.5rem; - 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); - - /* Initial state for staggered animation */ - opacity: 0; - transform: scale(0.8) translateY(10px); - animation: techItemFadeIn 0.4s ease-out forwards; -} - -/* Staggered animation delays for tech items */ -.tech-item:nth-child(1) { animation-delay: calc(var(--animation-delay-base) * 13); } -.tech-item:nth-child(2) { animation-delay: calc(var(--animation-delay-base) * 14); } -.tech-item:nth-child(3) { animation-delay: calc(var(--animation-delay-base) * 15); } -.tech-item:nth-child(4) { animation-delay: calc(var(--animation-delay-base) * 16); } -.tech-item:nth-child(5) { animation-delay: calc(var(--animation-delay-base) * 17); } -.tech-item:nth-child(n+6) { animation-delay: calc(var(--animation-delay-base) * 18); } - -.tech-item:hover { - transform: translateY(-2px) scale(1.05); - box-shadow: 0 6px 12px rgba(0,0,0,0.2); -} - /* Status Badges */ .current-status { padding: 0.375rem 0.75rem; @@ -392,38 +333,14 @@ margin-bottom: 0.25rem; } - .detail-block { - margin-bottom: 1rem; - padding: 0.5rem 0.25rem; - } - .detail-label { - font-size: 0.7rem; - margin-bottom: 0.375rem; - } .detail-value { font-size: 0.8rem; line-height: 1.4; } - .tech-section { - margin-top: 1.25rem; - padding-top: 1rem; - } - .tech-item { - padding: 0.25rem 0.5rem; - font-size: 0.7rem; - margin-bottom: 0.25rem; - - /* Simplified tech item animation */ - animation-name: techItemFadeIn; - animation-duration: 0.2s; - animation-timing-function: ease-out; - animation-delay: 0.6s; - animation-fill-mode: forwards; - } .current-status { padding: 0.375rem 0.75rem; diff --git a/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.html b/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.html index bf05e53..97ba39e 100644 --- a/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.html +++ b/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.html @@ -15,15 +15,7 @@
- -
-
CURRICULUM:
-
- @for (tech of training.techStack; track tech) { - {{ tech }} - } -
-
+
} diff --git a/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.ts b/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.ts index c74460f..cdd5453 100644 --- a/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.ts +++ b/src/app/pages/experience/operative-history/column-education-info/column-education-info.component.ts @@ -2,15 +2,16 @@ import { Component, signal } from '@angular/core'; import { type Experience } from '../../../../shared/models/experience.model'; 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'; @Component({ selector: 'app-column-education-info', - imports: [DetailBlockComponent, InfoCardComponent], + imports: [DetailBlockComponent, InfoCardComponent, TechStackComponent], templateUrl: './column-education-info.component.html', styleUrl: './column-education-info.component.css', }) export class ColumnEducationInfoComponent { - private readonly _currentTraining = signal({ + private _currentTraining = signal({ id: 'developer-training', title: 'DEVELOPER_TRAINING.EXE', classification: "Concepteur Développeur d'Applications (RNCP Level BAC+4)", @@ -28,5 +29,5 @@ export class ColumnEducationInfoComponent { isCurrent: true, } as const); - readonly currentTraining = this._currentTraining.asReadonly(); + currentTraining = this._currentTraining.asReadonly(); } diff --git a/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.css b/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.css index 1c5c9f0..cb1c516 100644 --- a/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.css +++ b/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.css @@ -150,64 +150,6 @@ color: var(--color-nier-text-light); } -/* Tech Stack Section */ -.tech-section { - margin-top: var(--card-gap); - padding-top: var(--card-gap); - border-top: 1px solid var(--color-nier-border); - position: relative; -} - -.tech-section::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 0; - height: 1px; - background: var(--color-nier-accent); - animation: expandLine 0.6s ease-out calc(var(--animation-delay-base) * 12) forwards; -} - -.tech-grid { - display: flex; - flex-wrap: wrap; - gap: 0.25rem; - margin-top: 0.5rem; -} - -/* Tech Items */ -.tech-item { - background: linear-gradient(135deg, var(--color-nier-accent) 0%, var(--color-nier-highlight) 100%); - color: var(--color-nier-text-light); - padding: 0.25rem 0.5rem; - 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); - - /* Initial state for staggered animation */ - opacity: 0; - transform: scale(0.8) translateY(10px); - animation: techItemFadeIn 0.4s ease-out forwards; -} - -/* Staggered animation delays for tech items */ -.tech-item:nth-child(1) { animation-delay: calc(var(--animation-delay-base) * 13); } -.tech-item:nth-child(2) { animation-delay: calc(var(--animation-delay-base) * 14); } -.tech-item:nth-child(3) { animation-delay: calc(var(--animation-delay-base) * 15); } -.tech-item:nth-child(4) { animation-delay: calc(var(--animation-delay-base) * 16); } -.tech-item:nth-child(5) { animation-delay: calc(var(--animation-delay-base) * 17); } -.tech-item:nth-child(n+6) { animation-delay: calc(var(--animation-delay-base) * 18); } - -.tech-item:hover { - transform: translateY(-2px) scale(1.05); - box-shadow: 0 6px 12px rgba(0,0,0,0.2); -} /* Visual Effects */ .corner-accent { diff --git a/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.html b/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.html index d298248..9ab6b6c 100644 --- a/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.html +++ b/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.html @@ -24,15 +24,7 @@
- -
-
TECH_STACK:
-
- @for (tech of experience.techStack; track tech) { - {{ tech }} - } -
-
+
} diff --git a/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.ts b/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.ts index 732956e..26561dd 100644 --- a/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.ts +++ b/src/app/pages/experience/operative-history/column-professional-info/column-professional-info.component.ts @@ -1,15 +1,16 @@ import { Component, signal } from '@angular/core'; import { Experience } from '../../../../shared/models/experience.model'; import { DetailBlockComponent } from '../shared/detail-block/detail-block.component'; +import { TechStackComponent } from '../tech-stack/tech-stack.component'; @Component({ selector: 'app-column-professional-info', - imports: [DetailBlockComponent], + imports: [DetailBlockComponent, TechStackComponent], templateUrl: './column-professional-info.component.html', styleUrl: './column-professional-info.component.css', }) export class ColumnProfessionalInfoComponent { - private readonly _experiences = signal([ + private _experiences = signal([ { id: 'senior-specialist', title: 'PYTHON_API_SPECIALIST.EXE', @@ -17,7 +18,7 @@ export class ColumnProfessionalInfoComponent { 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'] as const, + techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'], status: 'completed', statusLabel: 'ARCHIVED | SME_SPECIALIST', }, @@ -28,7 +29,7 @@ export class ColumnProfessionalInfoComponent { 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'] as const, + techStack: ['PYTHON', 'DJANGO', 'API', 'RPA'], status: 'completed', statusLabel: 'ARCHIVED | SME_SPECIALIST', }, @@ -39,7 +40,7 @@ export class ColumnProfessionalInfoComponent { 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'] as const, + techStack: ['SAAS', 'HRSD', 'CLIENT_MGMT'], status: 'completed', statusLabel: 'ARCHIVED | VIP_TIER', }, @@ -50,11 +51,11 @@ export class ColumnProfessionalInfoComponent { 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'] as const, + techStack: ['NETWORKING', 'SECURITY', 'DEPLOYMENT'], status: 'completed', statusLabel: 'ARCHIVED | MILITARY', }, - ] as const); + ]); - readonly experiences = this._experiences.asReadonly(); + experiences = this._experiences; } diff --git a/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.css b/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.css new file mode 100644 index 0000000..a8328b8 --- /dev/null +++ b/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.css @@ -0,0 +1,61 @@ +css/* tech-stack.component.css */ +.tech-section { + margin-top: 1.25rem; + padding-top: 1rem; +} + +.tech-grid { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin-top: 0.5rem; +} + +/* Tech Items with fallbacks */ +.tech-item { + background: linear-gradient(135deg, var(--color-nier-accent, #d4af37) 0%, var(--color-nier-highlight, #f4d03f) 100%); + color: var(--color-nier-text-light, #ffffff); + padding: 0.25rem 0.5rem; + font-size: 0.7rem; + font-family: var(--font-terminal, 'Courier New', monospace); + text-transform: uppercase; + letter-spacing: 0.05em; + border-radius: 3px; + white-space: nowrap; + transition: var(--transition-smooth, all 0.3s ease); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + + /* Initial state for staggered animation */ + opacity: 0; + transform: scale(0.8) translateY(10px); + animation: techItemFadeIn 0.4s ease-out forwards; +} + +/* Staggered animation delays for tech items */ +.tech-item:nth-child(1) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 13); } +.tech-item:nth-child(2) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 14); } +.tech-item:nth-child(3) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 15); } +.tech-item:nth-child(4) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 16); } +.tech-item:nth-child(5) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 17); } +.tech-item:nth-child(n+6) { animation-delay: calc(var(--animation-delay-base, 0.1s) * 18); } + +.tech-item:hover { + transform: translateY(-2px) scale(1.05); + box-shadow: 0 6px 12px rgba(0,0,0,0.2); +} + +.detail-label { + font-size: 0.7rem; + margin-bottom: 0.375rem; +} + +@keyframes techItemFadeIn { + from { + opacity: 0; + transform: scale(0.8) translateY(10px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} diff --git a/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.html b/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.html new file mode 100644 index 0000000..a00311b --- /dev/null +++ b/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.html @@ -0,0 +1,9 @@ +
+
{{label()}} +
+
+ @for (tech of techStack(); track tech) { + {{ tech }} + } +
+
diff --git a/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.ts b/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.ts new file mode 100644 index 0000000..e0a1499 --- /dev/null +++ b/src/app/pages/experience/operative-history/tech-stack/tech-stack.component.ts @@ -0,0 +1,12 @@ +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-tech-stack', + imports: [], + templateUrl: './tech-stack.component.html', + styleUrl: './tech-stack.component.css', +}) +export class TechStackComponent { + label = input.required(); + techStack = input.required(); +} diff --git a/src/app/shared/models/experience.model.ts b/src/app/shared/models/experience.model.ts index d132765..ff762ba 100644 --- a/src/app/shared/models/experience.model.ts +++ b/src/app/shared/models/experience.model.ts @@ -1,13 +1,13 @@ type Experience = { - readonly id: string; - readonly title: string; - readonly classification: string; - readonly objective: string; - readonly timeline: string; - readonly techStack: readonly string[]; - readonly status: 'current' | 'completed'; - readonly statusLabel: string; - readonly isCurrent?: boolean; + id: string; + title: string; + classification: string; + objective: string; + timeline: string; + techStack: string[]; + status: 'current' | 'completed'; + statusLabel: string; + isCurrent?: boolean; }; export { type Experience };