This commit is contained in:
Simon CATANESE 2024-05-12 21:32:30 +02:00
parent af562b7b10
commit aa59eaa3fc
18 changed files with 428 additions and 78 deletions

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,7 @@ export const isLoggedIn = async () => {
return response.data; return response.data;
} catch (error) { } catch (error) {
return error.response.data; return error.responseq;
} }
}; };
@ -15,7 +15,7 @@ export const login = async (username, password) => {
const response = await axios.post("/authenticate", { username, password }); const response = await axios.post("/authenticate", { username, password });
return response.data; return response.data;
} catch (error) { } catch (error) {
return error.response.data; return error.response;
} }
}; };
@ -25,6 +25,6 @@ export const logout = async () => {
return response.data; return response.data;
} catch (error) { } catch (error) {
return error.response.data; return error.response;
} }
}; };

View File

@ -5,7 +5,7 @@ import axios from "axios";
export const getRoom = async (_id) => { export const getRoom = async (_id) => {
try { try {
const response = await axios.get("/room", {_id}); const response = await axios.get("/room/"+{_id});
console.log(response.data) console.log(response.data)
return response.data; return response.data;
} catch (error) { } catch (error) {
@ -16,7 +16,7 @@ export const getRoom = async (_id) => {
export const getRooms = async () => { export const getRooms = async () => {
try { try {
const response = await axios.get("/room", {}); const response = await axios.get("/room");
console.log(response.data) console.log(response.data)
return response.data; return response.data;
} catch (error) { } catch (error) {
@ -24,3 +24,60 @@ export const getRooms = async () => {
return error.response.data; return error.response.data;
} }
}; };
export const getRoomStats = async () => {
try {
const response = await axios.get("/room/stats");
console.log(response.data)
return response.data;
} catch (error) {
console.log("ERROR", error.response.data)
return error.response.data;
}
};
export const formatRoomStats = (roomStats) => {
const global = formatRoomStatsGlobal(roomStats.global)
const rooms = Object.values(roomStats.rooms).map(formatRoom);
const years = roomStats.years;
return {
global,
rooms,
years
}
}
function formatRoomStatsGlobal(global){
const { rooms_count, items_count, total_price, average_price} = global;
const most_item_room = {
name: global.most_item_room.name,
count: global.most_item_room.count
};
const most_expensive_room = {
name: global.most_expensive_room.name,
price: global.most_expensive_room.price
};
return {
rooms_count,
items_count,
total_price,
average_price,
most_item_room,
most_expensive_room
};
}
function formatRoom(room) {
const { _id, name, items_count, room_price } = room;
return {
_id,
name,
items_count,
room_price
};
}

View File

@ -0,0 +1,40 @@
.diagram-container {
width: 100%; /* Utilise toute la largeur horizontale disponible */
margin-bottom: 20px;
}
.row-container {
display: flex;
align-items: flex-end; /* Alignement des éléments à la base */
margin-bottom: 10px;
}
.row {
background-color: #007bff;
height: 20px;
position: relative;
border-radius: 5px;
transition: width 0.5s ease;
}
.value {
position: absolute;
bottom: 50%; /* Alignement par rapport à la base de l'élément parent */
right: 0;
transform: translate(50%, 50%); /* Ajustement de la position */
color: white;
font-size: 12px;
padding: 5px;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.8);
}
.label {
width: 55px;
text-align: center;
font-size: 14px;
color: #333;
position: relative; /* Permet l'alignement des labels à la base */
bottom: 0; /* Alignement à la base */
}

View File

@ -0,0 +1,18 @@
.manContainer {
display: flex;
flex-direction: column;
height: 100%;
}
.topContainer {
display: flex;
flex-grow: 1;
}
.statsContainer, .listContainer {
flex-grow: 1;
}
.detailContainer {
height: 50%;
}

View File

@ -0,0 +1,47 @@
.room-stats-container {
display: flex;
flex-direction: column;
margin: 20px;
}
.text-stats {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.stats-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.stat {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.stat p {
margin: 0;
font-weight: bold;
}
.stat ul {
list-style: none;
padding: 0;
margin: 5px 0;
}
.stat ul li {
margin: 5px 0;
}
.diagram {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

View File

@ -0,0 +1,30 @@
import React, { useState, useEffect } from "react";
import { Col, Row } from "antd";
import '../assets/styles/diagram.css'
const Diagram = ({ data }) => {
const [diagramData, setDiagramData] = useState(data);
useEffect(() => {
setDiagramData(data);
}, [data]);
const maxValue = Math.max(...diagramData.map(item => item.value));
return (
<div className="diagram-container">
<h2>Prix total par an</h2>
{diagramData.map(({ name, value }) => (
<div key={name} className="row-container">
<div className="label">{name}</div>
<div className="row" style={{ width: `${(value / maxValue) * 100}%` }}>
<div className="value">{value}</div>
</div>
</div>
))}
</div>
);
};
export default Diagram;

View File

@ -8,34 +8,24 @@ import moment from 'moment';
const { Option } = Select; const { Option } = Select;
function formatItem(itemObj) {//_id, brand, model, room, price, purchaseDate, description, categories, createdAt, updatedAt, __v, link) { function formatItem(itemObj) {
let _id = itemObj._id const {_id,brand,model,room,price,purchaseDate,description,categories,createdAt,updatedAt,__v,link} = itemObj
let brand = itemObj.brand const formattedPurchaseDate = typeof purchaseDate === 'string' ? new Date(purchaseDate) : purchaseDate;
let model = itemObj.model
let room = itemObj.room return {
let price = itemObj.price
let purchaseDate = new Date(itemObj.purchaseDate)
let description = itemObj.description
let categories = itemObj.categories
let createdAt = itemObj.createdAt
let updatedAt = itemObj.updatedAt
let __v = itemObj.__v
let link = itemObj.link
let item = {
_id, _id,
brand, brand,
model, model,
room, room,
price, price,
purchaseDate, purchaseDate: formattedPurchaseDate,
description, description,
categories, categories,
createdAt, createdAt,
updatedAt, updatedAt,
__v, __v,
link link
} };
return item;
} }
export const FormUpdateItem = ({ itemId }) => { export const FormUpdateItem = ({ itemId }) => {

View File

@ -1,40 +1,10 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { searchAndResizeImage } from '../../api/image-request'
import '../../assets/styles/modal.css' import '../../assets/styles/modal.css'
import '../../assets/styles/itembox.css' import '../../assets/styles/itembox.css'
import FormUpdateItem from '../form/formUpdateItem'; import FormUpdateItem from '../form/formUpdateItem';
// Composant Image
const Image = ({ src, alt, request, _id }) => {
const [cacheUrl, setCacheUrl] = useState(null);
useEffect(() => {
const fetchData = async () => {
let cachedUrl = localStorage.getItem(_id);
if (!cachedUrl) {
try {
cachedUrl = await searchAndResizeImage(request);
localStorage.setItem(_id, cachedUrl);
console.log("Mise en cache de l'image avec l'ID : " + _id);
} catch (error) {
console.error("Erreur lors de la récupération de l'image : ", error);
}
}
setCacheUrl(cachedUrl);
};
fetchData();
}, [request, _id]);
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' />;
}
};
// Composant Description // Composant Description
const Description = ({ title, children }) => { const Description = ({ title, children }) => {

View File

@ -60,7 +60,10 @@ const Navbar = () => {
</Menu.Item>, </Menu.Item>,
<SubMenu key="3" title="Chambres"> <SubMenu key="3" title="Chambres">
{roomItems} {roomItems}
</SubMenu> </SubMenu>,
<Menu.Item key="4" >
<Link to="/rooms">Voir les chambres</Link>
</Menu.Item>,
]; ];

View File

@ -0,0 +1,34 @@
import React, { useEffect, useState } from 'react';
import { searchAndResizeImage } from '../../api/image-request'
// Composant Image
export const Image = ({ src, alt, request, _id }) => {
const [cacheUrl, setCacheUrl] = useState(null);
useEffect(() => {
const fetchData = async () => {
let cachedUrl = localStorage.getItem(_id);
if (!cachedUrl) {
try {
cachedUrl = await searchAndResizeImage(request);
localStorage.setItem(_id, cachedUrl);
console.log("Mise en cache de l'image avec l'ID : " + _id);
} catch (error) {
console.error("Erreur lors de la récupération de l'image : ", error);
}
}
setCacheUrl(cachedUrl);
};
fetchData();
}, [request, _id]);
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' />;
}
}

View File

@ -0,0 +1,46 @@
import React, { useState, useEffect } from "react";
import axios from 'axios'; // Assurez-vous que le chemin d'importation soit correct
import { ItemBox } from './ItemBox';
import { Space, DatePicker, Row, Col, Select, Input, InputNumber } from 'antd';
import '../../assets/styles/item-page.css'
const { RangePicker } = DatePicker;
const { Option } = Select;
const itemsPerPage = 8; // Nombre total d'items par page
const itemsPerRow = 4; // Nombre d'items par rangée
// Fonction pour diviser le tableau d'items en rangé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 = (roomsParam) => {
const [rooms, setRooms] = useState([]);
const [selectedRoom, setSelectedRoom] = useState('all');
useEffect(() => {
setRooms(roomsParam)
}, [roomsParam]);
const handleRoomChange = (value) => {
setSelectedRoom(value);
};
return (
<div className="list-container">
<div className="room-list">
{rooms.forEach((room) => {
<roomB
}) }
</div>
</div>
);
};

View File

@ -0,0 +1,69 @@
import React, { useEffect, useState } from 'react';
import { formatRoomStats } from '../../api/room'
import Diagram from '../diagram';
import '../../assets/styles/room-stats.css'
const RoomStats = ({statsParam}) => {
const [stats, setStats] = useState();
const [diagramValues, setDiagramValues] = useState([])
useEffect(() => {
if (statsParam.global) {
console.log(statsParam)
let formatedStats = formatRoomStats(statsParam)
setStats(formatedStats)
}
}, [statsParam]);
useEffect(() => {
if (stats) {
let table = Object.entries(stats.years)
.filter(([year, value]) => value !== 0) // Filtrer les entrées où Y n'est pas égal à 0
.map(([year, value]) => ({ name: year, value: value }));
setDiagramValues(table);
}
}, [stats])
return (
<div className="room-stats-container">
<div className="text-stats">
<h2>Statistiques des chambres</h2>
<div className="stats-info">
<div className="stat">
<p>Total des chambres :</p>
<span>{stats?.global?.rooms_count}</span>
</div>
<div className="stat">
<p>Total des articles :</p>
<span>{stats?.global?.items_count}</span>
</div>
<div className="stat">
<p>Prix total :</p>
<span>{stats?.global?.total_price}</span>
</div>
<div className="stat">
<p>La chambre avec le plus d'articles :</p>
<ul>
<li><span>{stats?.global.most_item_room?.name}</span></li>
<li>Nombre d'articles : <span>{stats?.global?.most_item_room?.count}</span></li>
</ul>
</div>
<div className="stat">
<p>La chambre la plus chè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>
</ul>
</div>
</div>
</div>
<div className="diagram">
<Diagram data={diagramValues} />
</div>
</div>
);
};
export default RoomStats;

View File

@ -0,0 +1,23 @@
import React, { useEffect, useState } from 'react';
import '../../assets/styles/itembox.css'
import { } from '../item/ItemBox'
// Composant Détails du Produit
export const RoomBox = ({ name, itemCount, roomPrice, _id }) => {
const [roomData, setRoomData] = useState();
useEffect(() => {
setRoomData({name, itemCount, roomPrice, _id})
}, [name, itemCount, roomPrice, _id]);
return (
<div className="product-details" >
<Description title={roomData.name} >
<Characteristic label="Nombre d'articles" value={roomData.itemCount} />
<Characteristic label="Prix total" value={roomData.roomPrice} />
</Description>
</div>
);
};

View File

@ -1,25 +0,0 @@
import React, { useEffect } from 'react';
import { useAuth } from "../hooks";
import { ItemPage } from "../components/item/ItemPage";
import { usePageTitle } from '../hooks/page-title-context';
export const RoomsStats = () => {
const { user } = useAuth();
const { setPageTitle } = usePageTitle();
// Mettre à jour le titre de la page dans le contexte
useEffect(() => {
setPageTitle("Toutes les rooms :");
}, [setPageTitle]);
useEffect(() => {
setPageTitle("Toutes les rooms :");
}, [setPageTitle]);
return (
<div className="gridContainer">
<div className="StatsContainer"></div>
</div>
);
};

46
src/pages/rooms.jsx Normal file
View File

@ -0,0 +1,46 @@
import React, { useEffect, useState } from 'react';
import '../assets/styles/room-page.css'
import { useAuth } from "../hooks";
import { formatRoomStats, getRooms, getRoomStats } from "../api/room";
import { usePageTitle } from '../hooks/page-title-context';
import RoomStats from '../components/rooms/room-stats';
export const Rooms = () => {
const { user } = useAuth();
const { setPageTitle } = usePageTitle();
const [stats, setStats] = useState({});
// Mettre à jour le titre de la page dans le contexte
useEffect(() => {
setPageTitle("Toutes les rooms :");
}, [setPageTitle]);
useEffect(() => {
const fetchData = async () => {
const roomsStatsResponse = await getRoomStats();
setStats(roomsStatsResponse);
};
fetchData();
}, []);
return (
<div className="manContainer">
<div className="topContainer">
<div className="statsContainer">
<RoomStats statsParam={stats} />
</div>
<div className="listContainer">
</div>
</div>
<div className="detailContainer">
<RoomStats statsParam={stats} />
</div>
</div>
);
};

View File

@ -3,6 +3,7 @@ import { Route, Routes } from "react-router-dom";
import { Home, Login, Register } from "./pages"; import { Home, Login, Register } from "./pages";
import { Items } from "./pages/items"; import { Items } from "./pages/items";
import { Rooms } from "./pages/rooms";
export const Router = () => ( export const Router = () => (
<Routes> <Routes>
@ -11,5 +12,6 @@ export const Router = () => (
<Route path="login" element={<Login />} /> <Route path="login" element={<Login />} />
<Route path="register" element={<Register />} /> <Route path="register" element={<Register />} />
<Route path="items" element={<Items />} /> <Route path="items" element={<Items />} />
<Route path="rooms" element={<Rooms />} />
</Routes> </Routes>
); );