commit final
This commit is contained in:
parent
6506d6817b
commit
1e3dcf340d
Binary file not shown.
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
21
README.md
21
README.md
@ -11,6 +11,27 @@ Victor DESCAMPS
|
|||||||
the tests sheet 'api/image-request.test.jsx' was commented out to avoid over-sending requests to the google API during development. Remember to uncomment the file if you want to run all the tests once, but remember to recomment the file if you want to run a series of tests.
|
the tests sheet 'api/image-request.test.jsx' was commented out to avoid over-sending requests to the google API during development. Remember to uncomment the file if you want to run all the tests once, but remember to recomment the file if you want to run a series of tests.
|
||||||
An over-sending of requests could get us blocked for the day by google.
|
An over-sending of requests could get us blocked for the day by google.
|
||||||
|
|
||||||
|
## Project summary (FR):
|
||||||
|
|
||||||
|
Nous n'avons pas réussi à mettre en œuvre toutes les fonctionnalités de l'interface utilisateur à temps. Le développement n'a pas non plus été rigoureusement test-driven.
|
||||||
|
|
||||||
|
résumé des fonctionnalités :
|
||||||
|
|
||||||
|
En se connectant, l'utilisateur peut charger la liste des articleset les filtrer par pièce, date, nom, etc. Il est possible de créer et de mettre à jour des articles.
|
||||||
|
(Il n'y a pas de confirmation de réussite ou d'échec de création/update. La page doit être actualisée pour voir les résultats.)
|
||||||
|
Une page permet de consulter les statistiques des pièces et la liste des pièces. Il est possible d'y créer de nouvelles. La mise à jour des piècesn'est pas encore implémentée.
|
||||||
|
|
||||||
|
Les icones AntDesign provoquent systématiquement une exception dans la console. Nous n'avons pas été en mesure de corriger ou éviter cette erreur. Les icones s'affichent bien, donc ça ne posera pas de problème à part la pollution de la console.
|
||||||
|
|
||||||
|
Nous avons voulu personnaliser un peu notre projet pour le différencier des autres, nous avons donc mis en place un système simple de recherche automatique d'images liées aux objets enregistrés, et une requête à l'API Inspirobot à chaque rechargement de la page sur la page d'accueil pour un accueil chaleureux.
|
||||||
|
|
||||||
|
Nous sommes conscients que ce projet ne correspond pas exactement aux attentes d'un développement full test-driven. Mais nous nous satisfaisons tout de même de nous être familiarisés avec js et React, avec les tests en général, et d'avoir pris du plaisir à développer ce petit projet.
|
||||||
|
|
||||||
|
Nous vous souhaitons une bonne correction et vous remercions pour vos cours.
|
||||||
|
|
||||||
|
Traduit avec DeepL.com (version gratuite)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Run Locally
|
## Run Locally
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
.diagram-container {
|
.diagram-container {
|
||||||
width: 100%; /* Utilise toute la largeur horizontale disponible */
|
width: 100%;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-container {
|
.row-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end; /* Alignement des éléments à la base */
|
align-items: flex-end;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,9 +20,9 @@
|
|||||||
|
|
||||||
.value {
|
.value {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 50%; /* Alignement par rapport à la base de l'élément parent */
|
bottom: 50%;
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: translate(50%, 50%); /* Ajustement de la position */
|
transform: translate(50%, 50%);
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@ -35,6 +35,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
position: relative; /* Permet l'alignement des labels à la base */
|
position: relative;
|
||||||
bottom: 0; /* Alignement à la base */
|
bottom: 0;
|
||||||
}
|
}
|
@ -87,12 +87,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Style pour les boutons de pagination */
|
|
||||||
.pagination button {
|
.pagination button {
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style pour les ItemBox */
|
|
||||||
.item-container .item-list .ant-col {
|
.item-container .item-list .ant-col {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
width: 250px; /* Largeur ajustable en fonction de votre mise en page */
|
width: 250px;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* CSS pour la fenêtre modale */
|
|
||||||
.modal {
|
.modal {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
.list-container {
|
.list-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height:500px;
|
max-height:500px;
|
||||||
overflow-y: auto; /* Activer le défilement vertical si nécessaire */
|
overflow-y: auto;
|
||||||
padding: 20px; /* Espace intérieur de la liste */
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-details {
|
.room-details {
|
||||||
width: 100%; /* Largeur de chaque boîte */
|
width: 100%;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 20px; /* Espace entre chaque boîte */
|
margin-bottom: 20px;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-details:hover {
|
.room-details:hover {
|
||||||
transform: translateY(-5px); /* Effet d'élévation au survol */
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-details .title {
|
.room-details .title {
|
||||||
|
@ -15,8 +15,8 @@ const Diagram = ({ data }) => {
|
|||||||
<div className="diagram-container">
|
<div className="diagram-container">
|
||||||
<h2>Prix total par an</h2>
|
<h2>Prix total par an</h2>
|
||||||
{diagramData.map(({ name, value }) => {
|
{diagramData.map(({ name, value }) => {
|
||||||
// Appliquer l'<EFBFBD>chelle logarithmique <EFBFBD> la valeur
|
// Appliquer l'<EFBFBD>chelle logarithmique sur la valeur
|
||||||
const scaledValue = Math.log(value + 1); // Ajouter 1 pour <EFBFBD>viter le logarithme de z<EFBFBD>ro
|
const scaledValue = Math.log(value + 1);
|
||||||
const scaledMaxValue = Math.log(maxValue + 1);
|
const scaledMaxValue = Math.log(maxValue + 1);
|
||||||
return (
|
return (
|
||||||
<div key={name} className="row-container">
|
<div key={name} className="row-container">
|
||||||
|
@ -32,8 +32,7 @@ export const FormCreateItem = ({ onClose }) => {
|
|||||||
values,
|
values,
|
||||||
);
|
);
|
||||||
console.log(response.data);
|
console.log(response.data);
|
||||||
// Fermer la fenêtre modale après soumission réussie
|
|
||||||
onClose();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,7 @@ export const FormUpdateItem = ({ itemId, onClose }) => {
|
|||||||
values,
|
values,
|
||||||
);
|
);
|
||||||
console.log(response.data);
|
console.log(response.data);
|
||||||
// Fermer la fenêtre modale après soumission réussie
|
|
||||||
onClose();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@ -87,7 +86,7 @@ export const FormUpdateItem = ({ itemId, onClose }) => {
|
|||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
initialValues={item} // Initialise le formulaire avec les valeurs de l'élément
|
initialValues={item}
|
||||||
>
|
>
|
||||||
<h1>Update Item</h1>
|
<h1>Update Item</h1>
|
||||||
<Form.Item label="Brand" name="brand">
|
<Form.Item label="Brand" name="brand">
|
||||||
|
58
src/components/form/formUpdateRoom.jsx
Normal file
58
src/components/form/formUpdateRoom.jsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {getRoom } from '../../api/room'
|
||||||
|
import { Form, Input, Button } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export const FormUpdateRoom = ({_id}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [room, setRoom] = useState();
|
||||||
|
|
||||||
|
//useEffect(() => {
|
||||||
|
// const fetchData = async () => {
|
||||||
|
// const roomsResponse = await getRoom(_id);
|
||||||
|
// setRoom(roomsResponse);
|
||||||
|
// console.log(roomsResponse);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// fetchData();
|
||||||
|
//}, [_id]);
|
||||||
|
|
||||||
|
//useEffect(() => {
|
||||||
|
// if (room) {
|
||||||
|
// form.setFieldsValue({
|
||||||
|
// ...room
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//}, [room, form]);
|
||||||
|
|
||||||
|
const onFinish = async (values) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${import.meta.env.VITE_API_URL}/room`,
|
||||||
|
values,
|
||||||
|
);
|
||||||
|
console.log(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form form={form} onFinish={onFinish}>
|
||||||
|
<Form.Item
|
||||||
|
label="Room Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: "Please input the room name!" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormUpdateRoom;
|
@ -6,16 +6,13 @@ import { Image } from "../parts/image";
|
|||||||
import { Description } from "../parts/description";
|
import { Description } from "../parts/description";
|
||||||
import { Characteristic } from "../parts/characteristic";
|
import { Characteristic } from "../parts/characteristic";
|
||||||
|
|
||||||
// Composant Détails du Produit
|
|
||||||
export const ItemBox = ({ model, brand, purchaseDate, price, _id }) => {
|
export const ItemBox = ({ model, brand, purchaseDate, price, _id }) => {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
// Fonction pour ouvrir la fenêtre modale
|
|
||||||
const openModal = () => {
|
const openModal = () => {
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fonction pour fermer la fenêtre modale
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
};
|
};
|
||||||
|
@ -25,9 +25,7 @@ function getItem(key, label) {
|
|||||||
|
|
||||||
const { SubMenu } = Menu;
|
const { SubMenu } = Menu;
|
||||||
|
|
||||||
// Component
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
//Hook calls
|
|
||||||
const [rooms, setRooms] = useState([]);
|
const [rooms, setRooms] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -56,7 +54,6 @@ const Navbar = () => {
|
|||||||
</Menu.Item>,
|
</Menu.Item>,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Rendu du composant Navbar
|
|
||||||
try {
|
try {
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
// Composant Caract<EFBFBD>ristique
|
|
||||||
export const Characteristic = ({ label, value }) => {
|
export const Characteristic = ({ label, value }) => {
|
||||||
return (
|
return (
|
||||||
<div className="characteristic">
|
<div className="characteristic">
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
// Composant Description
|
|
||||||
export const Description = ({ title, children }) => {
|
export const Description = ({ title, children }) => {
|
||||||
return (
|
return (
|
||||||
<div className="description">
|
<div className="description">
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { searchAndResizeImage } from '../../api/image-request'
|
import { searchAndResizeImage } from '../../api/image-request'
|
||||||
|
|
||||||
// Composant Image
|
|
||||||
export const Image = ({ src, alt, request, _id }) => {
|
export const Image = ({ src, alt, request, _id }) => {
|
||||||
const [cacheUrl, setCacheUrl] = useState(null);
|
const [cacheUrl, setCacheUrl] = useState(null);
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ export const Image = ({ src, alt, request, _id }) => {
|
|||||||
return <img src={src} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
return <img src={src} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
||||||
} else if (cacheUrl) {
|
} else if (cacheUrl) {
|
||||||
return <img src={cacheUrl} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
return <img src={cacheUrl} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
||||||
} else {
|
} else {//default image
|
||||||
return <img src={"https://media.discordapp.net/attachments/1164176196930637956/1167746303820840990/IMG_20231028_104620.jpg?ex=663ddefe&is=663c8d7e&hm=0985ce123fd1751f65388f7fefde5db6ce817e514e30f1d3c81eb28b15e78453&=&"} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
return <img src={"https://cdn.discordapp.com/attachments/1164176196930637956/1213857681362784266/dinorundiscord.gif?ex=6642258f&is=6640d40f&hm=cf0ca54df3e002a15049618a6654b22c5d0c7943dc420e84936635725aceb90f&"} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,10 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { FormCreateRoom } from "../../components/form/formCreateRoom";
|
import { FormCreateRoom } from "../../components/form/formCreateRoom";
|
||||||
//import { RoomUpdateForm } from './RoomUpdateForm';
|
import { FormUpdateRoom } from "../../components/form/formUpdateRoom";
|
||||||
|
|
||||||
export const RoomDetail = ({
|
export const RoomDetail = ({
|
||||||
selectedRoom,
|
selectedRoom,
|
||||||
onCreateFormSubmit,
|
onCreateFormSubmit,
|
||||||
onUpdateFormSubmit,
|
|
||||||
onBack,
|
onBack,
|
||||||
}) => {
|
}) => {
|
||||||
const [isUpdateFormVisible, setIsUpdateFormVisible] = useState(false);
|
const [isUpdateFormVisible, setIsUpdateFormVisible] = useState(false);
|
||||||
@ -16,12 +15,6 @@ export const RoomDetail = ({
|
|||||||
}
|
}
|
||||||
}, [selectedRoom]);
|
}, [selectedRoom]);
|
||||||
|
|
||||||
// Afficher le formulaire de mise <EFBFBD> jour lorsqu'une chambre est s<EFBFBD>lectionn<EFBFBD>e
|
|
||||||
const handleRoomClick = () => {
|
|
||||||
setIsUpdateFormVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Afficher le formulaire de cr<EFBFBD>ation lorsqu'on revient en arri<EFBFBD>re
|
|
||||||
const handleBackClick = () => {
|
const handleBackClick = () => {
|
||||||
setIsUpdateFormVisible(false);
|
setIsUpdateFormVisible(false);
|
||||||
onBack(null);
|
onBack(null);
|
||||||
@ -31,9 +24,9 @@ export const RoomDetail = ({
|
|||||||
<div className="room-detail">
|
<div className="room-detail">
|
||||||
{isUpdateFormVisible ? (
|
{isUpdateFormVisible ? (
|
||||||
<div>
|
<div>
|
||||||
<h2>Modifier une chambre</h2>
|
<h2>Modifier une chambre (non fonctionnel)</h2>
|
||||||
|
|
||||||
<FormCreateRoom onSubmit={onCreateFormSubmit} />
|
<FormUpdateRoom />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
|
@ -3,16 +3,8 @@ import "../../assets/styles/room-list.css";
|
|||||||
import { RoomBox } from "../../components/rooms/roomBox";
|
import { RoomBox } from "../../components/rooms/roomBox";
|
||||||
import { formatRoomStats } from "../../api/room";
|
import { formatRoomStats } from "../../api/room";
|
||||||
|
|
||||||
// Fonction pour diviser le tableau d'items en rang<EFBFBD>es
|
|
||||||
const chunkArray = (arr, size) => {
|
|
||||||
const chunkedArr = [];
|
|
||||||
for (let i = 0; i < arr.length; i += size) {
|
|
||||||
chunkedArr.push(arr.slice(i, i + size));
|
|
||||||
}
|
|
||||||
return chunkedArr;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Composant d'affichage de la page
|
|
||||||
export const RoomList = ({ statsParam, onRoomClick }) => {
|
export const RoomList = ({ statsParam, onRoomClick }) => {
|
||||||
const [rooms, setRooms] = useState([]);
|
const [rooms, setRooms] = useState([]);
|
||||||
const [selectedRoom, setSelectedRoom] = useState("all");
|
const [selectedRoom, setSelectedRoom] = useState("all");
|
||||||
|
@ -21,7 +21,7 @@ const RoomStats = ({statsParam}) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stats) {
|
if (stats) {
|
||||||
let table = Object.entries(stats.years)
|
let table = Object.entries(stats.years)
|
||||||
.filter(([year, value]) => value !== 0) // Filtrer les entrées où Y n'est pas égal à 0
|
.filter(([year, value]) => value !== 0)
|
||||||
.map(([year, value]) => ({ name: year, value: value }));
|
.map(([year, value]) => ({ name: year, value: value }));
|
||||||
setDiagramValues(table);
|
setDiagramValues(table);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user