wip
This commit is contained in:
parent
af562b7b10
commit
aa59eaa3fc
Binary file not shown.
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
@ -6,7 +6,7 @@ export const isLoggedIn = async () => {
|
||||
|
||||
return response.data;
|
||||
} 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 });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return error.response.data;
|
||||
return error.response;
|
||||
}
|
||||
};
|
||||
|
||||
@ -25,6 +25,6 @@ export const logout = async () => {
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return error.response.data;
|
||||
return error.response;
|
||||
}
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ import axios from "axios";
|
||||
|
||||
export const getRoom = async (_id) => {
|
||||
try {
|
||||
const response = await axios.get("/room", {_id});
|
||||
const response = await axios.get("/room/"+{_id});
|
||||
console.log(response.data)
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@ -16,7 +16,7 @@ export const getRoom = async (_id) => {
|
||||
|
||||
export const getRooms = async () => {
|
||||
try {
|
||||
const response = await axios.get("/room", {});
|
||||
const response = await axios.get("/room");
|
||||
console.log(response.data)
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@ -24,3 +24,60 @@ export const getRooms = async () => {
|
||||
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
|
||||
};
|
||||
}
|
40
src/assets/styles/diagram.css
Normal file
40
src/assets/styles/diagram.css
Normal 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 */
|
||||
}
|
18
src/assets/styles/room-page.css
Normal file
18
src/assets/styles/room-page.css
Normal 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%;
|
||||
}
|
47
src/assets/styles/room-stats.css
Normal file
47
src/assets/styles/room-stats.css
Normal 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);
|
||||
}
|
30
src/components/diagram.jsx
Normal file
30
src/components/diagram.jsx
Normal 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;
|
@ -8,34 +8,24 @@ import moment from 'moment';
|
||||
const { Option } = Select;
|
||||
|
||||
|
||||
function formatItem(itemObj) {//_id, brand, model, room, price, purchaseDate, description, categories, createdAt, updatedAt, __v, link) {
|
||||
let _id = itemObj._id
|
||||
let brand = itemObj.brand
|
||||
let model = itemObj.model
|
||||
let room = itemObj.room
|
||||
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 = {
|
||||
function formatItem(itemObj) {
|
||||
const {_id,brand,model,room,price,purchaseDate,description,categories,createdAt,updatedAt,__v,link} = itemObj
|
||||
const formattedPurchaseDate = typeof purchaseDate === 'string' ? new Date(purchaseDate) : purchaseDate;
|
||||
|
||||
return {
|
||||
_id,
|
||||
brand,
|
||||
model,
|
||||
room,
|
||||
price,
|
||||
purchaseDate,
|
||||
purchaseDate: formattedPurchaseDate,
|
||||
description,
|
||||
categories,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
__v,
|
||||
link
|
||||
}
|
||||
return item;
|
||||
};
|
||||
}
|
||||
|
||||
export const FormUpdateItem = ({ itemId }) => {
|
||||
|
@ -1,40 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { searchAndResizeImage } from '../../api/image-request'
|
||||
import '../../assets/styles/modal.css'
|
||||
import '../../assets/styles/itembox.css'
|
||||
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
|
||||
const Description = ({ title, children }) => {
|
||||
|
@ -60,7 +60,10 @@ const Navbar = () => {
|
||||
</Menu.Item>,
|
||||
<SubMenu key="3" title="Chambres">
|
||||
{roomItems}
|
||||
</SubMenu>
|
||||
</SubMenu>,
|
||||
<Menu.Item key="4" >
|
||||
<Link to="/rooms">Voir les chambres</Link>
|
||||
</Menu.Item>,
|
||||
|
||||
|
||||
];
|
||||
|
34
src/components/parts/image.jsx
Normal file
34
src/components/parts/image.jsx
Normal 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' />;
|
||||
}
|
||||
}
|
46
src/components/rooms/room-list.jsx
Normal file
46
src/components/rooms/room-list.jsx
Normal 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>
|
||||
);
|
||||
};
|
69
src/components/rooms/room-stats.jsx
Normal file
69
src/components/rooms/room-stats.jsx
Normal 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;
|
23
src/components/rooms/roomBox.jsx
Normal file
23
src/components/rooms/roomBox.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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
46
src/pages/rooms.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -3,6 +3,7 @@ import { Route, Routes } from "react-router-dom";
|
||||
|
||||
import { Home, Login, Register } from "./pages";
|
||||
import { Items } from "./pages/items";
|
||||
import { Rooms } from "./pages/rooms";
|
||||
|
||||
export const Router = () => (
|
||||
<Routes>
|
||||
@ -11,5 +12,6 @@ export const Router = () => (
|
||||
<Route path="login" element={<Login />} />
|
||||
<Route path="register" element={<Register />} />
|
||||
<Route path="items" element={<Items />} />
|
||||
<Route path="rooms" element={<Rooms />} />
|
||||
</Routes>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user