Files
portfolio/src/views/Projects.vue

449 lines
15 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue';
import { useBurgerStore } from '@/stores/burger'
//import { faThermometerQuarter } from '@fortawesome/free-solid-svg-icons';
const burger = useBurgerStore();
onMounted(() => {
burger.check();
const allTabs = document.querySelector('.all-tabs')
allTabs.addEventListener('scrollLeft', handleLeftArrow)
allTabs.addEventListener('scrollRight', handleRightArrow)
})
const items = ref([
{ id:1,
selected : false,
name : "Poils O pattes",
bg_image : "url('https://picsum.photos/id/20/200/300')"
},
{ id:2,
selected : false,
name : "Liste de course",
bg_image : "url('https://picsum.photos/id/23/200/300')"
},
{ id:3,
selected : false,
name : "Memory",
bg_image : "url('https://picsum.photos/id/23/200/300')"
},
{ id:4,
selected : true,
name : "A backend for an artist",
bg_image : "url('https://picsum.photos/id/23/200/300')"
},
]);
const handleLeftArrow = () =>{
for (const item of items.value) {
if (item.selected){
item.id - 1 == 0 ? changeSelectedTab(items.value.length) : changeSelectedTab(item.id - 1)
break
}
}
}
const handleRightArrow = () =>{
let exit = false ;
for (const item of items.value) {
if (item.selected && !exit){
item.id == items.value.length ? changeSelectedTab(1) : changeSelectedTab(item.id + 1)
exit = true
}
}
}
const changeSelectedTab = (id) => {
for(const item of items.value){
item.selected = item.id == id ? true : false
}
}
</script>
<template>
<!-- faire un v-for sur le tableau pour avoir le nom des onglets -->
<div class="content">
<div class="title">
<div class="logo"></div>
<h3>projects</h3>
</div>
<div class="tabs-bar">
<button class="left-arrow arrow" @click="handleLeftArrow()">
<font-awesome-icon icon="fa-solid fa-less-than" />
</button>
<ul class="all-tabs">
<li
v-for="item in items"
v-bind:key="item.id"
class="tab"
v-bind:class="{'active': item.selected}"
:style="{opacity : item.selected ? 1 : 0}"
:id = "'tab-' + item.id">
{{ item.name }}
</li>
</ul>
<button
class="right-arrow arrow"
@click="handleRightArrow()"
>
<font-awesome-icon icon="fa-solid fa-greater-than" />
</button>
</div>
<Transition name="fade">
<div class="project"
v-if="items[0].selected">
<p>Watch a presentation of this (awesome) project... But in the French language</p>
<iframe class="video" src="https://www.youtube.com/embed/Q3WiRGLeXSQ?start=2329" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h4>The project</h4>
<p class="project-summary">
<a href="https://poilsopattes.raffiskender.com" target="blank">
"Poils O'Pattes"</a> is a grooming salon management project undertaken with a team of 5 people over a duration of one month. I had the pleasure of contributing to it as a backend coder and scrum master...
</p>
<div class="image-container">
<div class="image-description">
<p>Here you can see the main page (Oh! there is a connection system!)</p>
<img src="../assets/images/pop/Screenshot_pop1.webp" alt="print screen of Poils O pattes weeb site">
</div>
<div class="image-description">
<p>Here you can see the page to fix an appointement to the salon</p>
<img src="../assets/images/pop/Screenshot_pop2.webp" alt="print screen of Poils O pattes weeb site">
</div>
<div class="image-description">
<p>Services page</p>
<img src="../assets/images/pop/Screenshot_pop3.webp" alt="print screen of Poils O pattes weeb site">
</div>
</div>
<div class="project-explanation">
<p>
The front-end was created by an awesome crew of 3 girls: <a href="#">Laura Pécheux</a>, <a href="#">Xénia Onescu</a>, and <a href="#">Mélodie Yamashita</a>. They built it using React, and they gaves it a really girly, catchy and vibrant design I really do appreciate.
</p>
<p>
But I didn't worked too much on the front, I worked hard on...
</p>
<h4>The back-end</h4>
<p>I have been working with <a href="#">Hocine Rehab</a> to design and code the back-end. Being two wasn't too much! This back-end controls:</p>
<ul>
<li>Users (their animals, their appointments, their status as stakeholders, clients, or admins).</li>
<li>Animals (their owners, breeds, and weights).</li>
<li>Appointments (which user with which stakeholder at what time and for which animal).</li>
<li>The services provided by the salon.</li>
<li>The products available in the salon.</li>
</ul>
<p>And we had one month to achieve and make it operational, stable, and efficient.</p>
<p>I won't describe every single detail (CPT, Taxonomies, Roles, Capabilities, ACL, or Custom Tables) because it would be too long. However, let me talk about the part I preferred: The schedule management.</p>
<img src="../assets/images/pop/back.webp" alt="image of the back-office">
<p>Please note the following:</p>
<ol>
<li>Past appointments are grayed out and cannot be modified.</li>
<li>Only one appointment can be modified at a time. The others are grayed out when one of them is being modified (as in the current case).</li>
<li>The dynamically generated dates are in French.</li>
<li>Some appointments are called "orphans." This is because when making an appointment on the front-end, they are not assigned. By clicking "honor this appointment," the groomer assigns it to themselves. It is then visible only to them (and the client on the front-end).</li>
<li>We have the ability to modify an appointment.</li>
<li>As a result, unavailable time slots are grayed out and cannot be selected. In the example above, 2 slots are taken on September 30th. If the date is changed, the time slots for the selected day automatically appear, and the unavailable time slots are grayed out. This mechanism is implemented using a JS-written API request.</li>
<li>The client select box is populated using a WordPress function, but...</li>
<li>The dog select box is automatically filled by fetching the client's dogs via the API for the currently selected client.</li>
</ol>
<p>One more detail: Everything you see has been programmed in vanilla JavaScript, vanilla CSS, HTML, and PHP (with WordPress functions). I hope you like it, as it has been quite challenging for us.</p>
</div>
</div>
<div class="project"
v-else-if ="items[1].selected">
<p>A presentation of this (finally useful) project can be watched in the video below... in the language of Shakespeare!</p>
<!-- <p>A presentation of this Watch a presentation of this (finally useful) project... And in the English language!</p> -->
<iframe class = "video" src="https://www.youtube.com/embed/3ZlYSN8dt5U" title="Presentation of (my) shopping list project" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> <h4>The project</h4>
<p class="project-summary">
The project <a href="https://liste-v2.raffiskender.com" target="_blank">"shopping list"</a> began when I was bored, thinking of what I could code. A teacher used to tell us "make little projects, like a list or a calculator...". So I thought, "Okay, let's create a shopping list for my wife that can also be used by my mom."
</p>
<p>
Finally, I am the one who uses it... But not as a shopping list: as a to-do list, and even more, as multiple to-do lists / reminders.
</p>
<div class="image-container">
<div class="image-description">
<p>You have to create an account, or you can use your own Google account.</p>
<img src="../assets/images/LDC/LDC_screenshot_1.png" alt="screenshot of shopping list web application">
</div>
<div class="image-description">
<p>Here are tabs you can name whatever you want, with lists inside.</p>
<img src="../assets/images/LDC/LDC_screenshot_2.png" alt="screenshot of shopping list web application">
</div>
<div class="image-description">
<p>Profile page.</p>
<img src="../assets/images/LDC/LDC_screenshot_3.png" alt="screenshot of shopping list web application">
</div>
</div>
<div class="project-explanation">
<h4>The front-end</h4>
<p>
The front-end was created in Vue. It incorporates a store, services to communicate with the backend (which happens often), a router, and plugins to sanitize the text, among other things.
</p>
<h4>The back-end</h4>
<p>The back-end manages:</p>
<ul>
<li>User management (with or without the Google sign-in API)</li>
<li>User's lists (encrypted json in a custom table)</li>
<li>Password reinitialization with custom tokens</li>
<li>Email sending for email verification</li>
</ul>
<p>In fact, this small application has all the features of a big one!</p>
</div>
</div>
<div class="project"
v-else-if ="items[2].selected">
<p>
That's a little project I made to keep my daughter occupied (but it worked only 2 weeks...). All pictures are fetched randomly from the Lorem Picsum free image bank (more details on my codepen for this part).
</p>
<p>Please note that this project will not work on mobile devices</p>
<p><a href="https://memory.raffiskender.com" target="_blank">See this project</a></p>
</div>
<div class="project"
v-else-if ="items[3].selected">
<!-- <p>A presentation of this Watch a presentation of this (finally useful) project... And in the English language!</p> -->
<p class="project-summary">
This website, created by my wife and me, showcases the remarkable work of a great artist. The purpose is to display his own creations, share news, introduce his students, and showcase their artistic endeavors.
</p>
<p class="project-summary">
I created a brief video to showcase and explain the backend I developed for him.
</p>
<iframe class= "video" src="https://www.youtube.com/embed/5oV5-I_yuYA" title="Wordpress backend for an artist" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
</Transition>
</div>
</template>
<style lang="scss" scoped>
@import '../style/colors.scss';
.content{
//position : relative;
& > .title {
& .logo{
background-image: url('../assets/maison.svg');
background-size: 70%;
}
}
}
h4{
color: $title-normal;
font-family: 'acme', Arial, Helvetica, sans-serif;
font-size:1.5em;
//margin-left: 2em;
margin-top: 1.25em;
text-align : center;
}
.project{
//position : absolute;
opacity : 1;
margin-top:1em;
transition : opacity 0.5s ease;
& .video{
display: block;
margin: 1.5em auto 0 auto;
width: 90%;
max-width: 20em;
aspect-ratio : 16/9;
}
& .unavailable{
pointer-events: none;
opacity: 0.5;
}
& a{
color: $hypertext-normal;
text-decoration: none;
transition : 0.25s;
&:hover{
text-decoration: underline;
color: $hypertext-light;
}
}
}
.show{
opacity : 1;
}
.tabs-bar {
height: 3em;
display:grid;
grid-template-columns: 2em calc(100% - 4em) 2em;
}
.project-explanation {
& > img{
display: block;
margin: 1em auto;
width: 90%;
aspect-ratio:1165/887
}
& ul{
margin: 0 0.75em 0 1.75em;
line-height: 1.5em;
& >li{
padding-left: 0.55em;
&::marker{
content: "•";
color: $title-normal;
// margin-:
}
}
}
& ol{
margin: 1em 0.75em 0 2.5em;
line-height: 1.6em;
& >li{
padding-left: 0.55em;
list-style:decimal;
&::marker{
// content: "•";
color: $title-normal;
// margin-:
}
}
}
}
ul.all-tabs {
//border:#ffffff 1px solid;
//flex-grow property to 2, flex-shrink to 1 and flex-basis to auto.
//display: flex;
position : relative;
//align-items: center;
//OverFlow is auto to keep scrolling on touching devices.
overflow-x: auto;
//Managing scroll bar
scroll-behavior: smooth;
//FireFox
scrollbar-width: none;
//Edge
-ms-overflow-style: none;
//Chrome & Safari
&::-webkit-scrollbar{
display: none;
}
& li.tab{
position : absolute;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
height:100%;
padding: 1.5em auto;
min-width: 100%;
background-size: 100%;
background-repeat: no-repeat;
background-position: center;
font-family: 'acme', Arial, Helvetica, sans-serif;
font-size: 1.75em;
//border:#0f0 solid 2px;
background-color:$page-bg;
color:$title-normal;
//color:rgb(0, 0, 0);
transition : opacity 1s;
}
}
button {
font-size: 1.3em;
background-color: #ffffff00;
transition: 0.3s;
border: none;
&.arrow{
margin: 0;
color:$action-normal;
transition : 0.25s;
&:hover{
color:$action-light;
cursor: pointer;
}
//border:1px red solid
}
}
p{
color : $main-normal;
//opacity: 1;
padding:1em 1.25em 0em 1em;
line-height: 1.5em;
animation: text 0.7s cubic-bezier(0.445, 0.05, 0.55, 0.95);
}
.image-container{
margin:auto;
& p {
min-height:100px;
padding: 0 0.75em;
text-indent:0;
display: flex;
align-items: center;
justify-content: center;
//border : solid 1px lime;
}
& img{
display: block;
margin:1em auto;
width: 90%;
}
}
.fade-leave-active {
transition: opacity 0.15s ease-out;
}
.fade-enter-active{
transition: opacity 0.35s ease-out;
transition-delay: 0.15s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
@keyframes text{
0%{
opacity : 0;
}
33%{
opacity : 0;
}
100%{
opacity:1;
}
}
@media (width > 370px){
.image-container{
display: flex;
justify-content:space-around;
flex-wrap: wrap;
& .image-description{
flex-basis: 300px;
flex-shrink: 0;
flex-grow: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
// aspect-ratio: ;
// height: 400px;
& > p{
// min-height:7em;
text-align: center;
text-indent:0;
}
}
}
}
</style>