502 lines
14 KiB
Vue
502 lines
14 KiB
Vue
<template>
|
|
<section id="experience" class="experience">
|
|
<div class="experience-container">
|
|
<h2 class="section-title">Compétences</h2>
|
|
<div class="skills-grid">
|
|
<div class="skill-card" v-for="(skill, index) in skills" :key="index">
|
|
<h3 class="skill-title">{{ skill.title }}</h3>
|
|
<div class="skill-tags">
|
|
<span class="skill-tag" v-for="(tag, tagIndex) in skill.tags" :key="tagIndex">{{ tag }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="experience-section">
|
|
<h2 class="section-title">Mon Parcours</h2>
|
|
|
|
<!-- Year filter buttons -->
|
|
<div class="filter-buttons">
|
|
<button
|
|
type="button"
|
|
class="filter-button"
|
|
:class="{ active: activeFilter === 'all' }"
|
|
@click.prevent="setFilter('all')">
|
|
Tous
|
|
</button>
|
|
<button
|
|
type="button"
|
|
v-for="year in availableYears"
|
|
:key="year"
|
|
class="filter-button"
|
|
:class="{ active: activeFilter === year }"
|
|
@click.prevent="setFilter(year)">
|
|
{{ year }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="timeline">
|
|
<!-- Work Experience Section -->
|
|
<div v-if="filteredJobs.length > 0" class="timeline-title">
|
|
<h3 class="timeline-heading">Expérience Professionnelle</h3>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
class="timeline-icon timeline-icon-work"
|
|
>
|
|
<path
|
|
d="M8 3c.132 0 .263 0 .393 0 7.107.007 10.372 6.167 11.917 11.917.078.287.156.575.234.863"
|
|
></path>
|
|
<path
|
|
d="M20.039 17.39c-.221-.89-.943-1.396-1.871-1.394a1.99 1.99 0 0 0-1.988 1.994v.012a1.998 1.998 0 0 0 2.004 1.998c.464 0 .91-.196 1.244-.548"
|
|
></path>
|
|
<path
|
|
d="M7.05 4.095c-1.815-.407-3.246-.089-4.028 1.007C1.082 7.547 1.603 12.19 4 16c2.492 0 4.623-1.33 6.234-3.219"
|
|
></path>
|
|
<path d="M11.167 12c1.174-1.525 2.272-3.747 2.272-6.8V3"></path>
|
|
<circle cx="16" cy="10" r="1"></circle>
|
|
</svg>
|
|
</div>
|
|
|
|
<div v-if="filteredJobs.length > 0" class="timeline-items">
|
|
<div
|
|
v-for="(job, index) in filteredJobs"
|
|
:key="'job-' + index"
|
|
class="timeline-card"
|
|
>
|
|
<div class="card-header">
|
|
<div>
|
|
<h3 class="card-title">{{ job.title }}</h3>
|
|
<p class="card-date">{{ job.period }}</p>
|
|
<div class="card-location">
|
|
<span>{{ job.location }}</span>
|
|
<span v-if="job.type">· {{ job.type }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-tags">
|
|
<span class="card-tag" v-for="(tag, tagIndex) in job.tags" :key="tagIndex">{{ tag }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<p>{{ job.description }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="filteredJobs.length > 0 && filteredEducation.length > 0" class="section-divider"></div>
|
|
|
|
<!-- Education Section -->
|
|
<div v-if="filteredEducation.length > 0">
|
|
<div class="timeline-title">
|
|
<h3 class="timeline-heading">Formation</h3>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
class="timeline-icon timeline-icon-education"
|
|
>
|
|
<path
|
|
d="M22 12A10 10 0 1 1 12 2a1 1 0 0 1 1 1v1a1 1 0 1 1-2 0V3.07A8 8 0 1 0 19 12h-3a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h1"
|
|
></path>
|
|
<path d="M11.8 22h.2a3 3 0 0 0 3-3v-1"></path>
|
|
<path
|
|
d="M20 19a2 2 0 0 0 2-2v-1a3 3 0 0 0-3-3h-1a2 2 0 0 0-2 2"
|
|
></path>
|
|
<circle cx="12" cy="12" r="3"></circle>
|
|
</svg>
|
|
</div>
|
|
|
|
<div class="timeline-items">
|
|
<div
|
|
v-for="(edu, index) in filteredEducation"
|
|
:key="'edu-' + index"
|
|
class="timeline-card education-card"
|
|
>
|
|
<div class="card-header">
|
|
<div>
|
|
<h3 class="card-title">{{ edu.title }}</h3>
|
|
<p class="card-date">{{ edu.period }}</p>
|
|
<div class="card-location">
|
|
<span>{{ edu.location }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<p>{{ edu.description }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: "Experience",
|
|
data() {
|
|
return {
|
|
activeFilter: 'all',
|
|
skills: [
|
|
{
|
|
title: "Langages & Frameworks",
|
|
tags: ["PHP", "Drupal", "Symfony", "Laravel"]
|
|
},
|
|
{
|
|
title: "Systèmes & DevOps",
|
|
tags: ["Docker", "Linux", "Git", "CI/CD"]
|
|
},
|
|
{
|
|
title: "Administration & Sécurité",
|
|
tags: ["Administration Systèmes", "Sécurité Web", "Monitoring", "SQL"]
|
|
}
|
|
],
|
|
jobs: [
|
|
{
|
|
title: "Administrateur Systèmes et Réseaux Web | STUDIO BESTIO",
|
|
period: "Février 2025 - Aujourd'hui",
|
|
location: "Nancy, France",
|
|
type: "Indépendant",
|
|
tags: ["Docker", "Linux"],
|
|
description: "Gestion de l'hébergement, de sauvegardes, de la maintenance et de la sécurité des sites web pour Studiobestio. Administration des serveurs, supervision des performances, gestion de la journalisation, mise en place de solutions de sécurité pour assurer une protection contre les cybermenaces. Garantir le bon fonctionnement, la sécurité et la fiabilité des sites web des clients.",
|
|
yearStart: 2025,
|
|
yearEnd: 2025
|
|
},
|
|
{
|
|
title: "Développeur PHP | GIE SIMA",
|
|
period: "Mai 2023 - Aujourd'hui",
|
|
location: "Nancy, France",
|
|
tags: ["PHP", "Drupal", "Docker"],
|
|
description: "Réecriture et maintenance d'une application pour la vente de contrats de santé ou prévoyance déstinée à des courtiers/conseillers.",
|
|
yearStart: 2023,
|
|
yearEnd: 2025
|
|
},
|
|
{
|
|
title: "Consultant | DAVIDSON CONSULTING",
|
|
period: "Février 2023 - Mai 2023",
|
|
location: "Nancy, France",
|
|
tags: ["PHP", "Drupal"],
|
|
description: "Développement d'applications clients",
|
|
yearStart: 2023,
|
|
yearEnd: 2023
|
|
},
|
|
{
|
|
title: "Développeur web | GANTOIS INDUSTRIES",
|
|
period: "Sept. 2021 - Avr. 2022",
|
|
location: "Saint-Dié, France",
|
|
type: "Intérimaire",
|
|
tags: ["PHP", "Symfony"],
|
|
description: "Développement d'applications métier",
|
|
yearStart: 2021,
|
|
yearEnd: 2022
|
|
},
|
|
{
|
|
title: "Développeur web | GANTOIS INDUSTRIES",
|
|
period: "Nov. 2020 - Sept. 2021",
|
|
location: "Saint-Dié, France",
|
|
type: "Alternance",
|
|
tags: ["PHP", "Symfony"],
|
|
description: "Développement et maintenance d'une application de contrôle qualité pour les pièces industrielles.",
|
|
yearStart: 2020,
|
|
yearEnd: 2021
|
|
},
|
|
{
|
|
title: "Développeur Full Stack | 2110 FINANCE",
|
|
period: "Juillet 2020",
|
|
location: "Saint-Dié, France",
|
|
type: "Stage",
|
|
tags: ["PHP", "Laravel"],
|
|
description: "Application de messagerie interne",
|
|
yearStart: 2020,
|
|
yearEnd: 2020
|
|
}
|
|
],
|
|
education: [
|
|
{
|
|
title: "Licence en application web et mobile | Université de Lorraine",
|
|
period: "2020 - 2021",
|
|
location: "Nancy, France",
|
|
description: "Formation en développement d'applications web et mobiles",
|
|
yearStart: 2020,
|
|
yearEnd: 2021
|
|
},
|
|
{
|
|
title: "DUT informatique | Université de Lorraine",
|
|
period: "2018 - 2020",
|
|
location: "Nancy, France",
|
|
description: "Formation en informatique générale avec spécialisation en développement",
|
|
yearStart: 2018,
|
|
yearEnd: 2020
|
|
},
|
|
{
|
|
title: "Baccalauréat Scientifique | Lycée Gaston Bachelard",
|
|
period: "2015 - 2018",
|
|
location: "Bar-sur-Aube, France",
|
|
description: "Baccalauréat avec option mathématiques",
|
|
yearStart: 2015,
|
|
yearEnd: 2018
|
|
}
|
|
]
|
|
};
|
|
},
|
|
computed: {
|
|
availableYears() {
|
|
const allYearsSet = new Set();
|
|
|
|
this.jobs.forEach(job => {
|
|
for (let year = job.yearStart; year <= job.yearEnd; year++) {
|
|
allYearsSet.add(year);
|
|
}
|
|
});
|
|
|
|
this.education.forEach(edu => {
|
|
for (let year = edu.yearStart; year <= edu.yearEnd; year++) {
|
|
allYearsSet.add(year);
|
|
}
|
|
});
|
|
|
|
return [...allYearsSet].sort();
|
|
},
|
|
filteredJobs() {
|
|
if (this.activeFilter === 'all') {
|
|
return this.jobs;
|
|
}
|
|
const filterYear = parseInt(this.activeFilter);
|
|
return this.jobs.filter(job => {
|
|
return filterYear >= job.yearStart && filterYear <= job.yearEnd;
|
|
});
|
|
},
|
|
// Filter education based on selected year
|
|
filteredEducation() {
|
|
if (this.activeFilter === 'all') {
|
|
return this.education;
|
|
}
|
|
const filterYear = parseInt(this.activeFilter);
|
|
return this.education.filter(edu => {
|
|
return filterYear >= edu.yearStart && filterYear <= edu.yearEnd;
|
|
});
|
|
}
|
|
},
|
|
methods: {
|
|
setFilter(filter) {
|
|
this.activeFilter = filter;
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.experience {
|
|
padding: 2rem 0;
|
|
scroll-margin-top: 4rem;
|
|
}
|
|
|
|
.experience-container {
|
|
max-width: 64rem;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 1.875rem;
|
|
font-weight: 700;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.skills-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 1.5rem;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.skill-card {
|
|
background-color: white;
|
|
padding: 1.5rem;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.skill-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1rem;
|
|
color: var(--duck-dark-blue);
|
|
}
|
|
|
|
.skill-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.skill-tag {
|
|
background-color: var(--duck-yellow);
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.experience-section {
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.filter-buttons {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.filter-button {
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 0.25rem;
|
|
background-color: #f3f4f6;
|
|
border: 1px solid #e5e7eb;
|
|
color: #4b5563;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.filter-button:hover {
|
|
background-color: #e5e7eb;
|
|
}
|
|
|
|
.filter-button.active {
|
|
background-color: var(--duck-blue);
|
|
color: white;
|
|
border-color: var(--duck-blue);
|
|
}
|
|
|
|
.timeline {
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.timeline-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.timeline-heading {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.timeline-icon {
|
|
width: 2rem;
|
|
height: 2rem;
|
|
}
|
|
|
|
.timeline-icon-work {
|
|
color: var(--duck-blue);
|
|
}
|
|
|
|
.timeline-icon-education {
|
|
color: var(--duck-orange);
|
|
}
|
|
|
|
.timeline-items {
|
|
margin-bottom: 2rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.timeline-card {
|
|
border-radius: 0.5rem;
|
|
border: 1px solid #e5e7eb;
|
|
background-color: white;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
width: 100%;
|
|
margin-bottom: 1rem;
|
|
border-left: 4px solid var(--duck-blue);
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
}
|
|
|
|
.timeline-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.education-card {
|
|
border-left: 4px solid var(--duck-orange);
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
padding: 1.5rem;
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.card-date {
|
|
font-size: 0.875rem;
|
|
color: var(--gray-500);
|
|
}
|
|
|
|
.card-location {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.875rem;
|
|
color: var(--gray-600);
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.card-tags {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.card-tag {
|
|
background-color: rgba(34, 139, 34, 0.2);
|
|
color: var(--text-color);
|
|
font-size: 0.75rem;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.25rem;
|
|
}
|
|
|
|
.card-content {
|
|
padding: 1.5rem;
|
|
padding-top: 0;
|
|
}
|
|
|
|
.card-content p {
|
|
color: var(--gray-700);
|
|
}
|
|
|
|
.section-divider {
|
|
height: 1px;
|
|
width: 100%;
|
|
background-color: #e5e7eb;
|
|
margin: 2.5rem 0;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.skills-grid {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
}
|
|
</style> |