Compare commits
7 Commits
4cf9069bf2
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
05b0a4514b | ||
560226b6a8 | |||
4e6a03df17 | |||
22ec7f8fdd | |||
baf31f7959 | |||
1e3dcf340d | |||
|
6506d6817b |
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,14 @@
|
||||
"",
|
||||
"\\src",
|
||||
"\\src\\api",
|
||||
"\\src\\hooks",
|
||||
"\\src\\pages",
|
||||
"\\tests-examples",
|
||||
"\\__tests__",
|
||||
"\\__tests__\\api",
|
||||
"\\__tests__\\pages",
|
||||
"\\__tests__\\pages\\authenticated"
|
||||
"\\src\\assets",
|
||||
"\\src\\components",
|
||||
"\\src\\components\\form",
|
||||
"\\src\\components\\item",
|
||||
"\\src\\components\\nav",
|
||||
"\\src\\components\\parts",
|
||||
"\\src\\components\\rooms"
|
||||
],
|
||||
"SelectedNode": "\\src\\pages\\rooms-stats.jsx",
|
||||
"SelectedNode": "\\README.md",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
19
README.md
19
README.md
@@ -11,6 +11,25 @@ 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.
|
||||
An over-sending of requests could get us blocked for the day by google.
|
||||
|
||||
## Project summary (FR):
|
||||
|
||||
Nous n'avons pas r<>ussi <20> mettre en <20>uvre toutes les fonctionnalit<69>s de l'interface utilisateur <20> temps. Le d<>veloppement n'a pas non plus <20>t<EFBFBD> rigoureusement test-driven.
|
||||
|
||||
r<EFBFBD>sum<EFBFBD> des fonctionnalit<69>s :
|
||||
|
||||
L'utilisateur peut cr<63>er un compte sur la page de login (navbar)
|
||||
En se connectant, l'utilisateur peut charger la liste des articles et les filtrer par pi<70>ce, date, nom, etc. Il est possible de cr<63>er, supprimer et de mettre <20> jour des articles.
|
||||
Une page permet de consulter les statistiques des pi<70>ces et la liste des pi<70>ces. Il est possible d'y cr<63>er de nouvelles, de les modifier ou de les supprimer (supprimant <20>galement les articles li<6C>s).
|
||||
Toute op<6F>ration d'<27>criture r<>ussie provoque un rechargement de page.
|
||||
|
||||
Les icones AntDesign provoquent syst<73>matiquement une exception dans la console. Nous n'avons pas <20>t<EFBFBD> en mesure de corriger ou <20>viter cette erreur. Les icones s'affichent bien, donc <20>a ne posera pas de probl<62>me <20> part la pollution de la console.
|
||||
|
||||
Nous avons voulu personnaliser un peu notre projet pour le diff<66>rencier des autres, nous avons donc mis en place un syst<73>me simple de recherche automatique d'images li<6C>es aux objets enregistr<74>s, et une requ<71>te <20> l'API Inspirobot <20> 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 <20>tre familiaris<69>s avec js et React, avec les tests en g<>n<EFBFBD>ral, et d'avoir pris du plaisir <20> d<>velopper ce petit projet.
|
||||
|
||||
Nous vous souhaitons une bonne correction et vous remercions pour vos cours.
|
||||
|
||||
|
||||
## Run Locally
|
||||
|
||||
|
@@ -1,35 +1,78 @@
|
||||
/*import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
||||
import { describe, it, vi, expect, beforeEach, afterEach } from "vitest";
|
||||
import { render, fireEvent, screen, cleanup } from "@testing-library/react";
|
||||
import { Login } from "../../../src/pages/index.js";
|
||||
import { ItemPage } from "../../../src/components/item/ItemPage.jsx"; // Assurez-vous d'importer correctement votre composant
|
||||
import { AuthenticationContext } from "../../../src/contexts/index.js";
|
||||
|
||||
describe('Items Page', () => {
|
||||
beforeEach(() => {
|
||||
// Se connecter à l'application avec le compte pseudo : Juvisi!0TEST et mdp : Juvisi!0TEST
|
||||
cy.visit('/');
|
||||
cy.findByPlaceholderText('username').type('Juvisi!0TEST');
|
||||
cy.findByPlaceholderText('password').type('Juvisi!0TEST');
|
||||
cy.findByText('submit').click();
|
||||
// Vérifier que la connexion est réussie et que vous êtes redirigé vers la page '/items'
|
||||
cy.findByText('Welcome Juvisi!0TEST').should('exist');
|
||||
});
|
||||
|
||||
it('creates an item', () => {
|
||||
// Créer un nouvel item
|
||||
cy.findByPlaceholderText('Item name').type('New Item');
|
||||
cy.findByText('Add Item').click();
|
||||
});
|
||||
|
||||
it('verifies the item is created', () => {
|
||||
// Vérifier que l'élément est ajouté à la liste
|
||||
cy.findByText('New Item').should('exist');
|
||||
});
|
||||
|
||||
it('deletes the item', () => {
|
||||
// Supprimer l'élément
|
||||
cy.findByText('New Item').siblings('button').click();
|
||||
});
|
||||
describe("Item Page with Authentication", () => {
|
||||
let rooms;
|
||||
beforeEach(() => {
|
||||
// Simuler la connexion de l'utilisateur
|
||||
const user = { username: "Juvisi!0TEST", password: "Juvisi!0TEST" };
|
||||
const loginFunction = vi.fn().mockResolvedValue(user); // Mock de la fonction de connexion
|
||||
rooms = [{ _id: "66425c9d8869396c3e853633", name: "test" }];
|
||||
render(
|
||||
<AuthenticationContext.Provider value={{ login: loginFunction }}>
|
||||
<ItemPage />
|
||||
</AuthenticationContext.Provider>,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
*/ //impossible de tester car element pas encore terminé
|
||||
|
||||
afterEach(cleanup); // Nettoyer après chaque test pour éviter les fuites de mémoire
|
||||
|
||||
it("renders without errors", () => {
|
||||
expect(screen.getByTestId("item-container")).toBeTruthy();
|
||||
});
|
||||
|
||||
/*it('allows filtering items', async () => {
|
||||
// Simuler une saisie dans le champ de recherche par nom
|
||||
fireEvent.change(screen.getByLabelText('Search by name:'), { target: { value: 'Item' } });
|
||||
// Attendre que les éléments filtrés soient rendus dans la liste
|
||||
await screen.findByText('Filtered Item');
|
||||
});*/
|
||||
|
||||
/*it('allows creating an item', async () => {
|
||||
// Simuler un clic sur le bouton "Create Item"
|
||||
fireEvent.click(screen.getByTestId('create-item-button'));
|
||||
// Attendre que le modal de création d'élément s'ouvre
|
||||
await screen.findByTestId('create-item-button');
|
||||
// Simuler une saisie dans les champs du formulaire de création d'élément
|
||||
fireEvent.change(screen.getByTestId("create-item-Brand-Input"), { target: { value: 'Brand X' } });
|
||||
fireEvent.change(screen.getByTestId("create-item-Model-Input"), { target: { value: 'Model Y' } });
|
||||
// Simuler un clic pour ouvrir le menu déroulant
|
||||
fireEvent.mouseDown(screen.getByTestId("create-item-Room-Select"));
|
||||
|
||||
// Attendre que les options du menu déroulant soient rendues
|
||||
await screen.findByText('test');
|
||||
|
||||
// Simuler un clic pour choisir l'option "test"
|
||||
fireEvent.click(screen.getByText('test'));
|
||||
|
||||
|
||||
// Simuler un clic sur le bouton "Submit" du formulaire
|
||||
fireEvent.click(screen.getByText('Submit'));
|
||||
// Attendre que l'élément soit ajouté à la liste
|
||||
await screen.findByText('Brand X');
|
||||
await screen.findByText('Model Y');
|
||||
});*/
|
||||
|
||||
/*it('allows updating an item', async () => {
|
||||
// Simuler un clic sur le bouton "Edit" de l'élément à mettre à jour
|
||||
fireEvent.click(screen.getByText('Edit'));
|
||||
// Attendre que le modal de mise à jour de l'élément s'ouvre
|
||||
await screen.findByText('Update Item');
|
||||
// Simuler une saisie dans les champs du formulaire de mise à jour d'élément
|
||||
fireEvent.change(screen.getByLabelText('Brand'), { target: { value: 'Updated Brand' } });
|
||||
fireEvent.change(screen.getByLabelText('Model'), { target: { value: 'Updated Model' } });
|
||||
// Simuler un clic sur le bouton "Submit" du formulaire de mise à jour
|
||||
fireEvent.click(screen.getByText('Submit'));
|
||||
// Attendre que l'élément mis à jour soit affiché dans la liste
|
||||
await screen.findByText('Updated Brand Updated Model');
|
||||
});*/
|
||||
|
||||
/*it('allows deleting an item', async () => {
|
||||
// Simuler un clic sur le bouton "Delete" de l'élément à supprimer
|
||||
fireEvent.click(screen.getByText('Delete'));
|
||||
// Attendre que l'élément soit supprimé de la liste
|
||||
await screen.findByText('Updated Brand Updated Model', { timeout: 5000, shouldNotExist: true });
|
||||
});*/
|
||||
});
|
||||
|
@@ -24,3 +24,45 @@ export const getItems = async () => {
|
||||
return error.response.data;
|
||||
}
|
||||
};
|
||||
|
||||
export const createItem = async (values) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${import.meta.env.VITE_API_URL}/item`,
|
||||
values,
|
||||
);
|
||||
console.log(response.data);
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const updateItem = async (_id, values) => {
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`${import.meta.env.VITE_API_URL}/item/${_id}`,
|
||||
values,
|
||||
);
|
||||
console.log(response.data);
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteItem = async (_id) => {
|
||||
try {
|
||||
const response = await axios.delete(
|
||||
`${import.meta.env.VITE_API_URL}/item/${_id}`
|
||||
);
|
||||
console.log(response.data);
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
@@ -5,7 +5,8 @@ import axios from "axios";
|
||||
|
||||
export const getRoom = async (_id) => {
|
||||
try {
|
||||
const response = await axios.get("/room/"+{_id});
|
||||
let id = _id.toString()
|
||||
const response = await axios.get(`/room/${id}`);
|
||||
console.log(response.data)
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -25,6 +26,56 @@ export const getRooms = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createRoom = async (values) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${import.meta.env.VITE_API_URL}/room`,
|
||||
values,
|
||||
);
|
||||
console.log(response.data);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const updateRoom = async(_id, values) => {
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`${import.meta.env.VITE_API_URL}/room/${_id}`,
|
||||
values,
|
||||
);
|
||||
console.log(response.data);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteRoom = async (_id) => {
|
||||
try {
|
||||
const itemsResponse = await axios.get(
|
||||
`${import.meta.env.VITE_API_URL}/item`,
|
||||
);
|
||||
const items = itemsResponse.data;
|
||||
items?.forEach(async (item) => {
|
||||
await axios.delete(
|
||||
`${import.meta.env.VITE_API_URL}/item/${item._id}`,
|
||||
);
|
||||
})
|
||||
|
||||
const response = await axios.delete(
|
||||
`${import.meta.env.VITE_API_URL}/room/${_id}`
|
||||
);
|
||||
console.log(response.data);
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const getRoomStats = async () => {
|
||||
try {
|
||||
const response = await axios.get("/room/stats");
|
||||
@@ -57,7 +108,7 @@ function formatRoomStatsGlobal(global){
|
||||
|
||||
const most_expensive_room = {
|
||||
name: global.most_expensive_room.name,
|
||||
price: global.most_expensive_room.price
|
||||
count: global.most_expensive_room.count
|
||||
};
|
||||
|
||||
return {
|
||||
|
@@ -1,11 +1,11 @@
|
||||
.diagram-container {
|
||||
width: 100%; /* Utilise toute la largeur horizontale disponible */
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.row-container {
|
||||
display: flex;
|
||||
align-items: flex-end; /* Alignement des <20>l<EFBFBD>ments <20> la base */
|
||||
align-items: flex-end;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
.value {
|
||||
position: absolute;
|
||||
bottom: 50%; /* Alignement par rapport <20> la base de l'<27>l<EFBFBD>ment parent */
|
||||
bottom: 50%;
|
||||
right: 0;
|
||||
transform: translate(50%, 50%); /* Ajustement de la position */
|
||||
transform: translate(50%, 50%);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 5px;
|
||||
@@ -35,6 +35,6 @@
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
position: relative; /* Permet l'alignement des labels <20> la base */
|
||||
bottom: 0; /* Alignement <20> la base */
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
}
|
@@ -78,8 +78,7 @@
|
||||
}
|
||||
|
||||
.item-list {
|
||||
width: 100%;
|
||||
max-width:50vw;
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
@@ -87,12 +86,10 @@
|
||||
}
|
||||
|
||||
|
||||
/* Style pour les boutons de pagination */
|
||||
.pagination button {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
/* Style pour les ItemBox */
|
||||
.item-container .item-list .ant-col {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@@ -9,7 +9,7 @@
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -57,3 +57,34 @@
|
||||
.product-details button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.edit-button,
|
||||
.delete-button {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.edit-button:hover,
|
||||
.delete-button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
@@ -1,4 +1,3 @@
|
||||
/* CSS pour la fen<65>tre modale */
|
||||
.modal {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@@ -1,22 +1,22 @@
|
||||
.list-container {
|
||||
width: 100%;
|
||||
max-height:500px;
|
||||
overflow-y: auto; /* Activer le d<>filement vertical si n<>cessaire */
|
||||
padding: 20px; /* Espace int<6E>rieur de la liste */
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.room-details {
|
||||
width: 100%; /* Largeur de chaque bo<62>te */
|
||||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px; /* Espace entre chaque bo<62>te */
|
||||
margin-bottom: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.room-details:hover {
|
||||
transform: translateY(-5px); /* Effet d'<27>l<EFBFBD>vation au survol */
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.room-details .title {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
import { AuthenticationProvider } from "../contexts";
|
||||
|
||||
export const Authenticated = ({ children }) => {
|
||||
|
||||
return <AuthenticationProvider>{children}</AuthenticationProvider>;
|
||||
};
|
@@ -15,8 +15,8 @@ const Diagram = ({ data }) => {
|
||||
<div className="diagram-container">
|
||||
<h2>Prix total par an</h2>
|
||||
{diagramData.map(({ name, value }) => {
|
||||
// Appliquer l'<27>chelle logarithmique <EFBFBD> la valeur
|
||||
const scaledValue = Math.log(value + 1); // Ajouter 1 pour <20>viter le logarithme de z<>ro
|
||||
// Appliquer l'<27>chelle logarithmique sur la valeur
|
||||
const scaledValue = Math.log(value + 1);
|
||||
const scaledMaxValue = Math.log(maxValue + 1);
|
||||
return (
|
||||
<div key={name} className="row-container">
|
||||
|
@@ -1,6 +1,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Form, Input, InputNumber, Button, Select, DatePicker } from "antd";
|
||||
import axios from "axios";
|
||||
import { createItem } from '../../api/item'
|
||||
|
||||
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
@@ -26,17 +29,10 @@ export const FormCreateItem = ({ onClose }) => {
|
||||
}, []);
|
||||
|
||||
const onFinish = async (values) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${import.meta.env.VITE_API_URL}/item`,
|
||||
values,
|
||||
);
|
||||
console.log(response.data);
|
||||
// Fermer la fenêtre modale après soumission réussie
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
let response = await createItem(values);
|
||||
if (response?.status >= 200 && response?.status < 300) {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@@ -1,20 +1,17 @@
|
||||
import React from "react";
|
||||
import { Form, Input, Button } from "antd";
|
||||
import { createRoom } from '../../api/room'
|
||||
|
||||
import axios from "axios";
|
||||
|
||||
export const FormCreateRoom = () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
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);
|
||||
}
|
||||
let response = await createRoom(values);
|
||||
if (response?.status >= 200 && response?.status < 300) {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import { Form, Input, InputNumber, Button, Select, DatePicker } from "antd";
|
||||
import axios from "axios";
|
||||
import { getRooms } from "../../api/room";
|
||||
import { getItem } from "../../api/item";
|
||||
import { getItem, updateItem } from "../../api/item";
|
||||
import moment from "moment";
|
||||
|
||||
const { Option } = Select;
|
||||
@@ -69,17 +69,10 @@ export const FormUpdateItem = ({ itemId, onClose }) => {
|
||||
}, [item, form]);
|
||||
|
||||
const onFinish = async (values) => {
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`${import.meta.env.VITE_API_URL}/item/${item._id}`,
|
||||
values,
|
||||
);
|
||||
console.log(response.data);
|
||||
// Fermer la fenêtre modale après soumission réussie
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
let response = await updateItem(item._id, values);
|
||||
if (response?.status >= 200 && response?.status < 300) {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const dateFormat = "YYYY-MM-DD";
|
||||
@@ -87,7 +80,7 @@ export const FormUpdateItem = ({ itemId, onClose }) => {
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
initialValues={item} // Initialise le formulaire avec les valeurs de l'élément
|
||||
initialValues={item}
|
||||
>
|
||||
<h1>Update Item</h1>
|
||||
<Form.Item label="Brand" name="brand">
|
||||
|
62
src/components/form/formUpdateRoom.jsx
Normal file
62
src/components/form/formUpdateRoom.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {getRoom, updateRoom, deleteRoom } 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 () => {
|
||||
console.log(_id)
|
||||
const roomsResponse = await getRoom(_id);
|
||||
setRoom(roomsResponse);
|
||||
console.log(roomsResponse);
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [_id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (room) {
|
||||
form.setFieldsValue({
|
||||
...room
|
||||
});
|
||||
}
|
||||
}, [room, form]);
|
||||
|
||||
const onFinish = async (values) => {
|
||||
let response = await updateRoom(_id, values);
|
||||
if (response?.status >= 200 && response?.status < 300) {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
let response = await deleteRoom(_id);
|
||||
if (response?.status >= 200 && response?.status < 300) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form form={form} onFinish={onFinish}>
|
||||
<button onClick={onDelete}> Supprimer </button>
|
||||
<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;
|
@@ -5,20 +5,26 @@ import FormUpdateItem from "../form/formUpdateItem";
|
||||
import { Image } from "../parts/image";
|
||||
import { Description } from "../parts/description";
|
||||
import { Characteristic } from "../parts/characteristic";
|
||||
import { deleteItem } from '../../api/item'
|
||||
|
||||
|
||||
// Composant Détails du Produit
|
||||
export const ItemBox = ({ model, brand, purchaseDate, price, _id }) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
// Fonction pour ouvrir la fenêtre modale
|
||||
const openModal = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
// Fonction pour fermer la fenêtre modale
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
let response = await deleteItem(_id);
|
||||
if (response?.status >= 200 && response?.status < 300) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
let productname = brand + " " + model;
|
||||
let formatedPrice = price + "€";
|
||||
@@ -26,30 +32,33 @@ export const ItemBox = ({ model, brand, purchaseDate, price, _id }) => {
|
||||
|
||||
let request = brand + " " + model;
|
||||
|
||||
return (
|
||||
<div className="product-details">
|
||||
<Description title={productname}>
|
||||
<Image request={request} _id={_id} alt="Product" />
|
||||
<Characteristic label="Model" value={model} />
|
||||
<Characteristic label="Brand" value={brand} />
|
||||
<Characteristic label="Purchase Date" value={formatedDate} />
|
||||
<Characteristic label="Price" value={formatedPrice} />
|
||||
{/* Bouton d'édition pour ouvrir la fenêtre modale */}
|
||||
<button onClick={openModal}>Edit</button>
|
||||
</Description>
|
||||
{/* Fenêtre modale */}
|
||||
{isModalOpen && (
|
||||
<div className="modal">
|
||||
<div className="modal-content">
|
||||
<span className="close" onClick={closeModal}>
|
||||
×
|
||||
</span>
|
||||
<FormUpdateItem itemId={_id}>
|
||||
{console.log("item ID :" + _id)}
|
||||
</FormUpdateItem>
|
||||
</div>
|
||||
return (
|
||||
<div className="product-details">
|
||||
<Description title={productname}>
|
||||
<Image request={request} alt="Product" />
|
||||
<Characteristic label="Model" value={model} />
|
||||
<Characteristic label="Brand" value={brand} />
|
||||
<Characteristic label="Purchase Date" value={formatedDate} />
|
||||
<Characteristic label="Price" value={formatedPrice} />
|
||||
{/* Boutons d'édition et de suppression */}
|
||||
<div className="button-group">
|
||||
<button className="edit-button" onClick={openModal}>Edit</button>
|
||||
<button className="delete-button" onClick={onDelete}>Delete</button>
|
||||
</div>
|
||||
</Description>
|
||||
{/* Fenêtre modale */}
|
||||
{isModalOpen && (
|
||||
<div className="modal">
|
||||
<div className="modal-content">
|
||||
<span className="close" onClick={closeModal}>
|
||||
×
|
||||
</span>
|
||||
<FormUpdateItem itemId={_id}>
|
||||
{console.log("item ID :" + _id)}
|
||||
</FormUpdateItem>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
@@ -68,9 +68,13 @@ export const ItemPage = () => {
|
||||
(filterPriceMin === "" || item.price >= parseFloat(filterPriceMin)) &&
|
||||
(filterPriceMax === "" || item.price <= parseFloat(filterPriceMax));
|
||||
const matchesDate =
|
||||
!filterDateRange ||
|
||||
filterDateRange.length === 0 ||
|
||||
(new Date(item.purchaseDate) >= filterDateRange[0] &&
|
||||
(filterDateRange[0] &&
|
||||
filterDateRange[1] &&
|
||||
new Date(item.purchaseDate) >= filterDateRange[0] &&
|
||||
new Date(item.purchaseDate) <= filterDateRange[1]);
|
||||
|
||||
return matchesRoom && matchesName && matchesPrice && matchesDate;
|
||||
});
|
||||
setFilteredItems(filtered);
|
||||
@@ -106,7 +110,55 @@ export const ItemPage = () => {
|
||||
|
||||
return (
|
||||
<div className="item-container">
|
||||
<div className="filters">{/* Filters here */}</div>
|
||||
<div className="filters">
|
||||
<div className="filter-group">
|
||||
<label htmlFor="roomSelect">Room:</label>
|
||||
<Select
|
||||
id="roomSelect"
|
||||
defaultValue="all"
|
||||
style={{ width: 120 }}
|
||||
onChange={handleRoomChange}
|
||||
>
|
||||
<Option value="all">All</Option>
|
||||
{rooms.map((room) => (
|
||||
<Option key={room._id} value={room._id}>
|
||||
{room.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="filter-group">
|
||||
<label htmlFor="nameInput">Search by name:</label>
|
||||
<Input
|
||||
id="nameInput"
|
||||
placeholder="Search by name"
|
||||
value={filterName}
|
||||
onChange={(e) => setFilterName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="filter-group">
|
||||
<label>Price:</label>
|
||||
<InputNumber
|
||||
placeholder="Min price"
|
||||
value={filterPriceMin}
|
||||
onChange={(value) => setFilterPriceMin(value)}
|
||||
/>
|
||||
<span> - </span>
|
||||
<InputNumber
|
||||
placeholder="Max price"
|
||||
value={filterPriceMax}
|
||||
onChange={(value) => setFilterPriceMax(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="filter-group">
|
||||
<label>Purchase Date:</label>
|
||||
<RangePicker
|
||||
format="YYYY-MM-DD"
|
||||
onChange={(dates) => setFilterDateRange(dates)}
|
||||
value={filterDateRange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Button to open create item modal */}
|
||||
<button onClick={() => setIsCreateItemModalOpen(true)}>
|
||||
|
@@ -25,19 +25,18 @@ function getItem(key, label) {
|
||||
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
// Component
|
||||
const Navbar = () => {
|
||||
//Hook calls
|
||||
const [rooms, setRooms] = useState([]);
|
||||
const [rooms, setRooms] = useState([]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log("NAVBAR EFFECT");
|
||||
isLoggedIn().then((user) => {
|
||||
if (user !== "Unauthorized") {
|
||||
getRooms().then((result) => {
|
||||
setRooms(result);
|
||||
});
|
||||
}
|
||||
if (user !== "Unauthorized") {
|
||||
getRooms().then((result) => {
|
||||
setRooms(result);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -56,11 +55,10 @@ const Navbar = () => {
|
||||
</Menu.Item>,
|
||||
];
|
||||
|
||||
// Rendu du composant Navbar
|
||||
try {
|
||||
return (
|
||||
<Menu
|
||||
defaultSelectedKeys={["1"]}
|
||||
defaultSelectedKeys={["0"]}
|
||||
defaultOpenKeys={["sub1"]}
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
// Composant Caract<63>ristique
|
||||
export const Characteristic = ({ label, value }) => {
|
||||
return (
|
||||
<div className="characteristic">
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
// Composant Description
|
||||
export const Description = ({ title, children }) => {
|
||||
return (
|
||||
<div className="description">
|
||||
|
@@ -2,18 +2,17 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { searchAndResizeImage } from '../../api/image-request'
|
||||
|
||||
// Composant Image
|
||||
export const Image = ({ src, alt, request, _id }) => {
|
||||
export const Image = ({ src, alt, request }) => {
|
||||
const [cacheUrl, setCacheUrl] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
let cachedUrl = localStorage.getItem(_id);
|
||||
let cachedUrl = localStorage.getItem(request);
|
||||
if (!cachedUrl) {
|
||||
try {
|
||||
cachedUrl = await searchAndResizeImage(request);
|
||||
localStorage.setItem(_id, cachedUrl);
|
||||
console.log("Mise en cache de l'image avec l'ID : " + _id);
|
||||
localStorage.setItem(request, cachedUrl);
|
||||
console.log("Mise en cache de l'image avec la requ<71>te : " + request);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la r<>cup<75>ration de l'image : ", error);
|
||||
}
|
||||
@@ -22,13 +21,13 @@ export const Image = ({ src, alt, request, _id }) => {
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [request, _id]);
|
||||
}, [request]);
|
||||
|
||||
if (src) {
|
||||
return <img src={src} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
||||
} else if (cacheUrl) {
|
||||
return <img src={cacheUrl} alt={alt} style={{ display: 'block', margin: 'auto' }} width='150px' height='150px' />;
|
||||
} else {
|
||||
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' />;
|
||||
} else {//default image
|
||||
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,30 +1,23 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FormCreateRoom } from "../../components/form/formCreateRoom";
|
||||
//import { RoomUpdateForm } from './RoomUpdateForm';
|
||||
import { FormUpdateRoom } from "../../components/form/formUpdateRoom";
|
||||
|
||||
export const RoomDetail = ({
|
||||
selectedRoom,
|
||||
onCreateFormSubmit,
|
||||
onUpdateFormSubmit,
|
||||
onBack,
|
||||
selectedRoomParam,
|
||||
onCreateFormSubmit
|
||||
}) => {
|
||||
const [isUpdateFormVisible, setIsUpdateFormVisible] = useState(false);
|
||||
const [selectedRoom, setSelectedRoom] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRoom) {
|
||||
setIsUpdateFormVisible(true);
|
||||
if (selectedRoomParam) {
|
||||
setSelectedRoom(selectedRoomParam)
|
||||
setIsUpdateFormVisible(true);
|
||||
}
|
||||
}, [selectedRoom]);
|
||||
}, [selectedRoomParam]);
|
||||
|
||||
// Afficher le formulaire de mise <20> jour lorsqu'une chambre est s<>lectionn<6E>e
|
||||
const handleRoomClick = () => {
|
||||
setIsUpdateFormVisible(true);
|
||||
};
|
||||
|
||||
// Afficher le formulaire de cr<63>ation lorsqu'on revient en arri<72>re
|
||||
const handleBackClick = () => {
|
||||
setIsUpdateFormVisible(false);
|
||||
onBack(null);
|
||||
setIsUpdateFormVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -32,8 +25,8 @@ export const RoomDetail = ({
|
||||
{isUpdateFormVisible ? (
|
||||
<div>
|
||||
<h2>Modifier une chambre</h2>
|
||||
|
||||
<FormCreateRoom onSubmit={onCreateFormSubmit} />
|
||||
<button onClick={handleBackClick}>Annuler </button>
|
||||
<FormUpdateRoom _id={selectedRoom} />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
|
@@ -3,19 +3,10 @@ import "../../assets/styles/room-list.css";
|
||||
import { RoomBox } from "../../components/rooms/roomBox";
|
||||
import { formatRoomStats } from "../../api/room";
|
||||
|
||||
// Fonction pour diviser le tableau d'items en rang<6E>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 }) => {
|
||||
const [rooms, setRooms] = useState([]);
|
||||
const [selectedRoom, setSelectedRoom] = useState("all");
|
||||
|
||||
useEffect(() => {
|
||||
if (statsParam.rooms) {
|
||||
|
@@ -21,7 +21,7 @@ const RoomStats = ({statsParam}) => {
|
||||
useEffect(() => {
|
||||
if (stats) {
|
||||
let table = Object.entries(stats.years)
|
||||
.filter(([year, value]) => value !== 0) // Filtrer les entr<74>es o<> Y n'est pas <20>gal <20> 0
|
||||
.filter(([year, value]) => value !== 0)
|
||||
.map(([year, value]) => ({ name: year, value: value }));
|
||||
setDiagramValues(table);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ const RoomStats = ({statsParam}) => {
|
||||
<p>La chambre la plus ch<EFBFBD>re :</p>
|
||||
<ul>
|
||||
<li><span>{stats?.global?.most_expensive_room?.name}</span></li>
|
||||
<li>Prix total : <span>{stats?.global?.most_expensive_room?.price}</span></li>
|
||||
<li>Prix total : <span>{stats?.global?.most_expensive_room?.count}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -6,7 +6,8 @@ import { Characteristic } from "../parts/characteristic";
|
||||
export const RoomBox = ({ room, onRoomClick }) => {
|
||||
const handleBoxClick = () => {
|
||||
onRoomClick(room._id);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="room-details" onClick={handleBoxClick}>
|
||||
|
@@ -16,7 +16,11 @@ export const Login = () => {
|
||||
if (response && !response.success) {
|
||||
setError(response.error);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onRegister = () => {
|
||||
window.location.href = '/register'
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -36,6 +40,7 @@ export const Login = () => {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button type="submit">submit</button>
|
||||
<button onClick={onRegister}>Register</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@@ -21,7 +21,8 @@ export const Register = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Register page</h1>
|
||||
<h1>Register page</h1>
|
||||
<p>Password must include a capital letter, a digit and a symbol</p>
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<form onSubmit={onSubmit}>
|
||||
<input
|
||||
|
@@ -30,7 +30,7 @@ export const Rooms = () => {
|
||||
const handleRoomClick = (roomId) => {
|
||||
console.log("Clicked room ID:", roomId);
|
||||
setSelectedRoom(roomId);
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="manContainer">
|
||||
@@ -44,8 +44,7 @@ export const Rooms = () => {
|
||||
</div>
|
||||
<div className="detailContainer">
|
||||
<RoomDetail
|
||||
selectedRoom={selectedRoom}
|
||||
onBack={handleRoomClick}
|
||||
selectedRoomParam={selectedRoom}
|
||||
></RoomDetail>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user