some css fix and websocket fix for item update
This commit is contained in:
698
package-lock.json
generated
698
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
|
||||
58
src/App.css
58
src/App.css
@@ -1,42 +1,36 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
.user-btn {
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 1000;
|
||||
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
.log-menu {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
right: 12px;
|
||||
width: 280px;
|
||||
background: #111;
|
||||
border: 1px solid #333;
|
||||
padding: 12px;
|
||||
z-index: 1001;
|
||||
box-shadow: 0 8px 30px rgba(0,0,0,.4);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
.log-menu button,
|
||||
.log-menu input {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
132
src/App.jsx
132
src/App.jsx
@@ -8,6 +8,8 @@ import { API_BASE } from './config.js'
|
||||
|
||||
import Login from './components/login.jsx'
|
||||
import MainApp from './components/MainApp.jsx'
|
||||
import AdminWidget from './components/widget/adminWidget.jsx'
|
||||
|
||||
|
||||
export const ClientContext = createContext(null);
|
||||
|
||||
@@ -22,9 +24,8 @@ export default function App() {
|
||||
const [updateCurrentPassword, setUpdateCurrentPassword] = useState("");
|
||||
const [updateNewPassword, setUpdateNewPassword] = useState("");
|
||||
|
||||
const accessToken = localStorage.getItem("access_tokens");
|
||||
const tokens = JSON.parse(accessToken)
|
||||
const accessTokenSingle = tokens[0];
|
||||
|
||||
const [logMenuOpen, setLogMenuOpen] = useState(false)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -60,6 +61,7 @@ async function checkAuth() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function logoutUser() {
|
||||
|
||||
try {
|
||||
@@ -87,6 +89,7 @@ async function logoutUser() {
|
||||
}
|
||||
|
||||
async function logoutAllSessions() {
|
||||
if (!accessTokenSingle) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/auth/logout_all_devices`, {
|
||||
@@ -113,10 +116,68 @@ async function logoutAllSessions() {
|
||||
}
|
||||
}
|
||||
|
||||
function RegisterUser() {
|
||||
|
||||
let [username, setUserName] = useState("");
|
||||
let [password, setPassword] = useState("");
|
||||
let [displayName, setDisplayName] = useState("");
|
||||
|
||||
let hotel_ids = [1];
|
||||
|
||||
function registerUser(username, password, hotel_ids, displayname) {
|
||||
|
||||
const payload = {
|
||||
username,
|
||||
password,
|
||||
hotel_ids,
|
||||
displayname
|
||||
}
|
||||
|
||||
console.log("Reg")
|
||||
|
||||
const res = fetch(`${API_BASE}/auth/register`, {
|
||||
method: "PUT",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify(payload),
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="CreateUserDiv">
|
||||
<input
|
||||
value={username}
|
||||
onChange={e => setUserName(e.target.value)}
|
||||
placeholder="NomDeCompte"
|
||||
type="text" />
|
||||
|
||||
<input
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
placeholder="Mot de Passe"
|
||||
type="text" />
|
||||
|
||||
<input
|
||||
value={displayName}
|
||||
onChange={e => setDisplayName(e.target.value)}
|
||||
placeholder="Nome afficher"
|
||||
type="text" />
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
registerUser(username, password, hotel_ids, displayName)
|
||||
}
|
||||
>
|
||||
Créer l'utilisateur
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function updatePassword(e) {
|
||||
async function updatePassword(e) {
|
||||
e.preventDefault();
|
||||
|
||||
//try
|
||||
@@ -126,7 +187,7 @@ function updatePassword(e) {
|
||||
newpassword : updateNewPassword
|
||||
}
|
||||
|
||||
const res = fetch(`${API_BASE}/auth/update_password`, {
|
||||
const res = await fetch(`${API_BASE}/auth/update_password`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -146,33 +207,46 @@ function updatePassword(e) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={logoutUser}>disconnect</button>
|
||||
<button onClick={logoutAllSessions}>disconnect all session</button>
|
||||
<button className="user-btn" onClick={() => setLogMenuOpen(o => !o)}>
|
||||
⚙️
|
||||
</button>
|
||||
|
||||
<form onSubmit={updatePassword}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={updateUsername}
|
||||
onChange={(e) => setUpdateUsername(e.target.value)}
|
||||
/>
|
||||
{logMenuOpen && (
|
||||
<div className='log-menu'>
|
||||
<button onClick={logoutUser}>disconnect</button>
|
||||
<button onClick={logoutAllSessions}>disconnect all session</button>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="MDP actuel"
|
||||
value={updateCurrentPassword}
|
||||
onChange={(e) => setUpdateCurrentPassword(e.target.value)}
|
||||
/>
|
||||
<form onSubmit={updatePassword}>
|
||||
<h1>Update Password</h1>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={updateUsername}
|
||||
onChange={(e) => setUpdateUsername(e.target.value)}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nouveau MDP"
|
||||
value={updateNewPassword}
|
||||
onChange={(e) => setUpdateNewPassword(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="MDP actuel"
|
||||
value={updateCurrentPassword}
|
||||
onChange={(e) => setUpdateCurrentPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<button type="submit">Uppdate</button>
|
||||
</form>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nouveau MDP"
|
||||
value={updateNewPassword}
|
||||
onChange={(e) => setUpdateNewPassword(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Uppdate</button>
|
||||
</form>
|
||||
<RegisterUser/>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{loggedIn ? (
|
||||
<MainApp resClientId={clientId} />
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { HotelProvider, useHotel } from "./HotelContext";
|
||||
import { HotelProvider, useHotel } from "./context/HotelContext";
|
||||
import RoomWidget from "./widget/roomWidget"
|
||||
import ChatWidget from "./widget/chatWidget"
|
||||
import InventoryWidget from "./widget/inventoryWidget";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import "./MainApp.css"
|
||||
import AdminWidget from "./widget/adminWidget";
|
||||
|
||||
export default function MainAppWrapper({resClientId}) {
|
||||
const accessToken = localStorage.getItem("access_tokens");
|
||||
const accessToken = localStorage.getItem("access_tokens");
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<HotelProvider accessToken={accessToken} resClientId={resClientId}>
|
||||
@@ -18,25 +21,29 @@ export default function MainAppWrapper({resClientId}) {
|
||||
|
||||
function MainApp() {
|
||||
const accessToken = localStorage.getItem("access_tokens");
|
||||
const { rooms, conversations, users } = useHotel();
|
||||
const VIEW = {
|
||||
ROOMS: "rooms",
|
||||
INVENTORY: "inventory",
|
||||
ADMIN: "admin",
|
||||
};
|
||||
|
||||
const { rooms } = useHotel();
|
||||
const { conversations } = useHotel();
|
||||
const { users } = useHotel();
|
||||
|
||||
const [view, setView] = useState(VIEW.ROOMS)
|
||||
|
||||
return (
|
||||
<div style={{ padding: "2rem" }}>
|
||||
<h1>Welcome to MyHotel!</h1>
|
||||
<div>
|
||||
<div className="toolbar">
|
||||
<button className="toolbutton" onClick={() => setView(VIEW.ROOMS)}>Rooms</button>
|
||||
<button className="toolbutton" onClick={() => setView(VIEW.INVENTORY)}>Inventory</button>
|
||||
</div>
|
||||
|
||||
<h2>Access token</h2>
|
||||
<pre>{accessToken}</pre>
|
||||
<h3>Rooms: {rooms.length}</h3>
|
||||
<h3>Conversations: {conversations.length}</h3>
|
||||
<h3>Users: {users.length}</h3>
|
||||
<h2>Dashboard</h2>
|
||||
<section className="main">
|
||||
|
||||
|
||||
<RoomWidget roomlist={rooms}/>
|
||||
<ChatWidget convlist={conversations}/>
|
||||
<InventoryWidget/>
|
||||
<AdminWidget/>
|
||||
{view === VIEW.ROOMS && <RoomWidget />}
|
||||
{view === VIEW.INVENTORY && <InventoryWidget />}
|
||||
<ChatWidget convlist={conversations} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
.main {
|
||||
display: grid;
|
||||
grid-template-columns: 70% 30%;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
background-color: rgb(160, 149, 199);
|
||||
}
|
||||
|
||||
|
||||
.main > * {
|
||||
min-width: 0; /* prevents overflow bugs */
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
justify-content: space-around;
|
||||
height: 10vh;
|
||||
height: 5vh;
|
||||
}
|
||||
|
||||
.toolbutton {
|
||||
margin: .5rem;
|
||||
height: 80%;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import {createContext, useContext,
|
||||
useState, useEffect,
|
||||
useRef
|
||||
} from "react";
|
||||
import { API_BASE } from "../config";
|
||||
import { API_BASE } from "../../config";
|
||||
|
||||
const HotelContext = createContext(null);
|
||||
|
||||
@@ -16,6 +16,9 @@ export function HotelProvider({ accessToken, children, resClientId}) {
|
||||
const [rooms, setRooms] = useState([]);
|
||||
const [conversations, setConversations] = useState([]);
|
||||
const [messagesByConvId, setMessagesByConvId] = useState({});
|
||||
const [items, SetItems] = useState([]);
|
||||
|
||||
|
||||
|
||||
const [users, setUsers] = useState([]);
|
||||
const [usersById, setUsersById] = useState({});
|
||||
@@ -55,7 +58,6 @@ export function HotelProvider({ accessToken, children, resClientId}) {
|
||||
|
||||
// CHAT
|
||||
|
||||
|
||||
async function createConversation(name) {
|
||||
|
||||
const payload = {
|
||||
@@ -278,11 +280,24 @@ export function HotelProvider({ accessToken, children, resClientId}) {
|
||||
break;
|
||||
}
|
||||
|
||||
case "item_update": {
|
||||
SetItems(prev =>
|
||||
prev.map(i =>
|
||||
i.id === msg.item_id
|
||||
? { ...i, amount: msg.number }
|
||||
: i
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "chat_message": {
|
||||
appendMessage(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
default:
|
||||
console.warn("Unhandled WS message bruuuuh:", msg);
|
||||
}
|
||||
@@ -290,7 +305,7 @@ export function HotelProvider({ accessToken, children, resClientId}) {
|
||||
|
||||
// --- ADMIN PORTAL ----
|
||||
|
||||
function registerUser(username,password,hotel_ids, displayname) {
|
||||
function registerUser(username, password, hotel_ids, displayname) {
|
||||
|
||||
const payload = {
|
||||
username,
|
||||
@@ -351,10 +366,11 @@ async function addHotelUser(user_id,hotel_ids) {
|
||||
if (!accessToken) return;
|
||||
|
||||
async function load() {
|
||||
const [roomsData, convData, usersData] = await Promise.all([
|
||||
const [roomsData, convData, usersData, itemsData] = await Promise.all([
|
||||
fetchRooms(),
|
||||
fetchConversations(),
|
||||
fetchHotelUsers(),
|
||||
fetchHotelInventory(),
|
||||
|
||||
]);
|
||||
|
||||
@@ -362,7 +378,7 @@ async function addHotelUser(user_id,hotel_ids) {
|
||||
setRooms(roomsData);
|
||||
setConversations(convData);
|
||||
setUsers(usersData);
|
||||
|
||||
SetItems(itemsData);
|
||||
//getHotelList();
|
||||
|
||||
}
|
||||
@@ -370,7 +386,7 @@ async function addHotelUser(user_id,hotel_ids) {
|
||||
load();
|
||||
|
||||
|
||||
//console.log("USERS 2 =",usersById)
|
||||
console.log("here are hte items" + items)
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -383,7 +399,8 @@ async function addHotelUser(user_id,hotel_ids) {
|
||||
//if (!accessTokenSingle || !clientId) return;
|
||||
|
||||
const ws = new WebSocket(
|
||||
`http://localhost:7080/auth/ws/${accessTokenSingle}`
|
||||
`ws://localhost:7080/auth/ws/${accessTokenSingle}`
|
||||
//`wss://79.137.75.155/hotel-demo/api/auth/ws/${accessTokenSingle}`
|
||||
);
|
||||
|
||||
wsRef.current = ws;
|
||||
@@ -423,6 +440,7 @@ async function addHotelUser(user_id,hotel_ids) {
|
||||
users,
|
||||
usersById,
|
||||
clientId,
|
||||
items,
|
||||
|
||||
fetchHotelUsers,
|
||||
updateRoomStatus,
|
||||
65
src/components/context/RoomContext.jsx
Normal file
65
src/components/context/RoomContext.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import {createContext, useContext,
|
||||
useState, useEffect,
|
||||
useRef } from "react";
|
||||
import { API_BASE } from "../../config";
|
||||
|
||||
const RoomContext = createContext(null);
|
||||
|
||||
export function UseRoom() {
|
||||
return useContext(RoomContext);
|
||||
}
|
||||
|
||||
export function RoomProvider({ accessToken, children, resclientid}) {
|
||||
|
||||
const [rooms, setRooms] = useState([]);
|
||||
|
||||
async function fetchRooms() {
|
||||
const res = await fetch( `${API_BASE}/rooms/rooms`, {
|
||||
headers: { Authorization: `Bearer ${accessTokenSingle}` },
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function updateRoomStatus(roomId, status) {
|
||||
await fetch(`${API_BASE}/rooms/clean_db_update/${roomId}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessTokenSingle}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ status }),
|
||||
});
|
||||
// refresh cached rooms
|
||||
//const updated = await fetchRooms();
|
||||
//setRooms(updated);
|
||||
}
|
||||
|
||||
//TODO: update room status function
|
||||
|
||||
useEffect(() => {
|
||||
if (!accessToken) return;
|
||||
|
||||
async function load() {
|
||||
const [roomsData] = await Promise.all([
|
||||
fetchRooms()
|
||||
]);
|
||||
|
||||
setRooms(roomsData);
|
||||
}
|
||||
|
||||
load();
|
||||
})
|
||||
|
||||
return(
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
rooms,
|
||||
|
||||
|
||||
updateRoomStatus
|
||||
}}
|
||||
></RoomContext.Provider>
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
@@ -40,22 +40,104 @@ export default function Login({ onLogin }) {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: add login button for test1 and test2
|
||||
|
||||
async function LoginTest1() {
|
||||
//e.preventDefault();
|
||||
//setError("");
|
||||
|
||||
const device_id = "147ac10b-58cc-4372-a567-0e02b2c3d479";
|
||||
|
||||
let username = "test1"
|
||||
let password = "test1"
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/auth/create_refresh`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "react-app",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ username, password, device_id }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
setError("Invalid credentials or server error.");
|
||||
return;
|
||||
}
|
||||
|
||||
//const data = await res.json(); // server returns short-lived token
|
||||
//localStorage.setItem("access_token", data.access_token);
|
||||
|
||||
if (onLogin) await onLogin(); // notify App to show MainApp
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Network error.");
|
||||
}
|
||||
}
|
||||
|
||||
async function LoginTest2() {
|
||||
//e.preventDefault();
|
||||
//setError("");
|
||||
|
||||
const device_id = "147ac10b-58cc-4372-a567-0e02b2c3d479";
|
||||
|
||||
let username = "test2"
|
||||
let password = "test2"
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/auth/create_refresh`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "react-app",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ username, password, device_id }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
setError("Invalid credentials or server error.");
|
||||
return;
|
||||
}
|
||||
|
||||
//const data = await res.json(); // server returns short-lived token
|
||||
//localStorage.setItem("access_token", data.access_token);
|
||||
|
||||
if (onLogin) await onLogin(); // notify App to show MainApp
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Network error.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Login</button>
|
||||
{error && <p style={{ color: "red" }}>{error}</p>}
|
||||
</form>
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Login</button>
|
||||
{error && <p style={{ color: "red" }}>{error}</p>}
|
||||
</form>
|
||||
<div>
|
||||
<button onClick={LoginTest1} >login test 1</button>
|
||||
<button onClick={LoginTest2} >login test 2</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,46 +1,34 @@
|
||||
.messagebox{
|
||||
|
||||
|
||||
/* Room grid */
|
||||
.roomGrid {
|
||||
margin: 5px;
|
||||
height: 90vh;
|
||||
overflow-y: auto;
|
||||
border-radius: 10px;
|
||||
background-color: var(--card) ;
|
||||
display: grid;
|
||||
padding: 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Room card */
|
||||
.card {
|
||||
background-color: var(--panel) ;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* your messages */
|
||||
.msg--mine {
|
||||
align-self: flex-end;
|
||||
background-color: #464b43;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* others */
|
||||
.msg--theirs {
|
||||
align-self: flex-start;
|
||||
background-color: #473f3f;
|
||||
}
|
||||
.grid {
|
||||
display: flex;
|
||||
justify-items: space-around;
|
||||
gap: 1px;
|
||||
background-color: #777777;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
gap: 1px;
|
||||
background-color: #a3a3a3;
|
||||
padding: 5px;
|
||||
|
||||
}
|
||||
|
||||
.card input {
|
||||
width: 90%;
|
||||
box-sizing: border-box; /* important */
|
||||
max-width: 100%; /* prevents overflow */
|
||||
.roomCard input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { useHotel } from "../HotelContext";
|
||||
import { useHotel } from "../context/HotelContext";
|
||||
|
||||
|
||||
export default function AdminWidget() {
|
||||
|
||||
@@ -1,16 +1,89 @@
|
||||
.chatWidget{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.convcard {
|
||||
background-color: brown;
|
||||
.chatWidget {
|
||||
margin :5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
background: var(--card);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.convlist {
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: right;
|
||||
background-color: darkcyan;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
overflow-y: auto;
|
||||
height: 20%;
|
||||
}
|
||||
|
||||
.convcard {
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--panel);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.convcard:hover {
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.senderBox{
|
||||
display: flex;
|
||||
height: 10%;
|
||||
}
|
||||
|
||||
.senderBox * {
|
||||
border: 0px;
|
||||
padding: 0px;
|
||||
|
||||
}
|
||||
|
||||
.textbox{
|
||||
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.sendbutton{
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
|
||||
.messageCard {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
background: var(--messageCard);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
.msg {
|
||||
max-width: 70%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.msg--mine {
|
||||
align-self: flex-end;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.msg--theirs {
|
||||
align-self: flex-start;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.createConvBox {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.createConvBox > * {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useHotel } from "../HotelContext";
|
||||
|
||||
import { useHotel } from "../context/HotelContext";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRef } from "react";
|
||||
|
||||
@@ -110,17 +111,63 @@ function SenderBox({ conv_id, onSend, disabled }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div hidden={disabled} className="senderbox">
|
||||
<div hidden={disabled} className="senderBox">
|
||||
<input
|
||||
className="textbox"
|
||||
value={text}
|
||||
onChange={e => setText(e.target.value)}
|
||||
onKeyDown={e => e.key === "Enter" && handleSend() }
|
||||
placeholder="Type a message…"
|
||||
/>
|
||||
<button onClick={handleSend}>Send</button>
|
||||
<button className="sendbutton" onClick={handleSend}>Send</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ConvCard({id, title, onOpenConv}) {
|
||||
return(
|
||||
<div className="convcard" onClick={() => onOpenConv(id)} >
|
||||
<h3>ConvId : {id} </h3>
|
||||
<h3>Name : {title}</h3>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MessagesBox({ conv_id }) {
|
||||
const { messagesByConvId, usersById, clientId } = useHotel();
|
||||
const messages = messagesByConvId[conv_id] ?? [];
|
||||
|
||||
console.log(messagesByConvId);
|
||||
console.log(usersById);
|
||||
|
||||
if (messages.length === 0) {
|
||||
return <div className="messagesbox empty">No messages</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="messageCard">
|
||||
{messages
|
||||
.slice()
|
||||
.sort((a, b) => new Date(a.sent_at) - new Date(b.sent_at))
|
||||
.map(msg => {
|
||||
const isMine = msg.sender_id === clientId;
|
||||
return (
|
||||
<div key={msg.sent_at}
|
||||
className={`msg ${isMine ? "msg--mine" : "msg--theirs"}`}>
|
||||
<p>
|
||||
<strong>Sender:</strong>{" "}
|
||||
{usersById[msg.sender_id] ?? "Unknown user"}
|
||||
</p>
|
||||
|
||||
<p>{msg.content}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AddUsersMenu({ convId, usersById, fetchConvUsers, onValidate, onClose }) {
|
||||
const [selected, setSelected] = useState(new Set());
|
||||
|
||||
@@ -197,53 +244,10 @@ function CreateConvMenu({onSend}) {
|
||||
<input
|
||||
value= {nameText}
|
||||
onChange= {element => setNameText(element.target.value)}
|
||||
onKeyDown={e => e.key === "Enter" && handleSend() }
|
||||
placeholder="Ma Conversation"
|
||||
/>
|
||||
<button onClick= {handleSend}>Send</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ConvCard({id, title, onOpenConv}) {
|
||||
return(
|
||||
<div className="convcard">
|
||||
<h3>ConvId : {id} </h3>
|
||||
<h3>Name : {title}</h3>
|
||||
<p onClick={() => onOpenConv(id)}> GET MESSAAGE</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MessagesBox({ conv_id }) {
|
||||
const { messagesByConvId, usersById, clientId } = useHotel();
|
||||
const messages = messagesByConvId[conv_id] ?? [];
|
||||
|
||||
console.log(messagesByConvId);
|
||||
console.log(usersById);
|
||||
|
||||
if (messages.length === 0) {
|
||||
return <div className="messagesbox empty">No messages</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="messagebox">
|
||||
{messages
|
||||
.slice()
|
||||
.sort((a, b) => new Date(a.sent_at) - new Date(b.sent_at))
|
||||
.map(msg => {
|
||||
const isMine = msg.sender_id === clientId;
|
||||
return (
|
||||
<div key={msg.sent_at}
|
||||
className={`msg ${isMine ? "msg--mine" : "msg--theirs"}`}>
|
||||
<p>
|
||||
<strong>Sender:</strong>{" "}
|
||||
{usersById[msg.sender_id] ?? "Unknown user"}
|
||||
</p>
|
||||
|
||||
<p>{msg.content}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +1,23 @@
|
||||
|
||||
.grid {
|
||||
margin: 5px ;
|
||||
align-items: start;
|
||||
grid-auto-rows: max-content;
|
||||
height: 90vh;
|
||||
overflow-y: auto;
|
||||
border-radius: 10px;
|
||||
background-color: var(--card) ;
|
||||
padding: 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.itemcard {
|
||||
background-color: var(--panel) ;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -1,54 +1,46 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {HotelProvider, useHotel} from "../HotelContext";
|
||||
import {HotelProvider, useHotel} from "../context/HotelContext";
|
||||
import "./inventoryWidget.css";
|
||||
|
||||
export default function InventoryWidget({}) {
|
||||
const {
|
||||
fetchHotelInventory
|
||||
} = useHotel();
|
||||
//const { fetchHotelInventory } = useHotel();
|
||||
//const [items, setItems] = useState([]);
|
||||
|
||||
const [items, setItems] = useState([]);
|
||||
|
||||
|
||||
/*
|
||||
useEffect(() => {
|
||||
|
||||
async function loadInventory() {
|
||||
const data = await fetchHotelInventory()
|
||||
setItems(data);
|
||||
}
|
||||
|
||||
loadInventory()
|
||||
console.log ("loaded inventory")
|
||||
|
||||
}, [])
|
||||
*/
|
||||
|
||||
const { items } = useHotel();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid">
|
||||
{items.map(item => (
|
||||
<ItemCard
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
name={item.name}
|
||||
amount={item.amount}
|
||||
/>
|
||||
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<CreateItemMenu/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="grid">
|
||||
{items.map(item => (
|
||||
<ItemCard
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
name={item.name}
|
||||
amount={item.amount}
|
||||
/>
|
||||
))}
|
||||
<CreateItemMenu/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
function ItemCard({id, name, amount}) {
|
||||
const [itemAmount, setItemAmount] = useState(amount);
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const {updateItemAmount} = useHotel();
|
||||
|
||||
function submit(item_id, item_amount) {
|
||||
@@ -59,10 +51,12 @@ function ItemCard({id, name, amount}) {
|
||||
|
||||
return (
|
||||
<div className="itemcard">
|
||||
<div>{name}</div>
|
||||
<h3>{name}</h3>
|
||||
|
||||
{!editing ?(
|
||||
<p onClick={() => setEditing(true)} /*style={ cursor: "pointer" }*/>Amount :{amount}</p>
|
||||
<h5 onClick={() => setEditing(true)} >
|
||||
Amount :{amount}
|
||||
</h5>
|
||||
):(
|
||||
<div>
|
||||
<input
|
||||
@@ -74,10 +68,8 @@ function ItemCard({id, name, amount}) {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
//TODO: same pb as convlist, update when typing in the create menu
|
||||
@@ -86,25 +78,23 @@ function CreateItemMenu() {
|
||||
const [itemAmount, setItemAmount] = useState(0);
|
||||
const {createItem} = useHotel();
|
||||
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!itemName.trim() | !itemAmount.trim() ) return;
|
||||
|
||||
createItem(itemName, itemAmount);
|
||||
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div className="itemcard" >
|
||||
<input
|
||||
type="text"
|
||||
onChange={element => setItemName(element.target.value)}
|
||||
placeholder="Nom de l'objet"
|
||||
type="text"
|
||||
onChange={element => setItemName(element.target.value)}
|
||||
placeholder="Nom de l'objet"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
onChange={element => setItemAmount(element.target.value)}
|
||||
placeholder="Montant d'objet(s)"
|
||||
type="text"
|
||||
onChange={element => setItemAmount(element.target.value)}
|
||||
placeholder="Montant d'objet(s)"
|
||||
/>
|
||||
<button onClick={handleSubmit}>Creer</button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
|
||||
import "./RoomWidget.css";
|
||||
import { useHotel } from "../HotelContext";
|
||||
import { useHotel } from "../context/HotelContext";
|
||||
import { useState } from "react";
|
||||
export default function RoomWidget({ roomlist }) {
|
||||
|
||||
|
||||
|
||||
|
||||
export default function RoomWidget({}) {
|
||||
|
||||
const { rooms } = useHotel();
|
||||
|
||||
return (
|
||||
<div className="grid">
|
||||
{roomlist.map(room => (
|
||||
<div className="roomGrid">
|
||||
{rooms.map(room => (
|
||||
<RoomCard
|
||||
key={room.id}
|
||||
id = {room.id}
|
||||
@@ -19,8 +24,6 @@ export default function RoomWidget({ roomlist }) {
|
||||
}
|
||||
|
||||
function RoomCard({ number, status ,id}) {
|
||||
|
||||
|
||||
//FIXME: this shouldn't use hotel context, instead : set the state once on load
|
||||
const { updateRoomStatus } = useHotel();
|
||||
const [editing, setEdtiting] = useState(false);
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
//export const API_BASE = "http://79.137.75.155:8080";
|
||||
export const API_BASE = "http://localhost:7080";
|
||||
//export const API_BASE = "https://79.137.75.155/hotel-demo/api/";
|
||||
export const API_BASE = "http://localhost:7080";
|
||||
|
||||
|
||||
//export const API_BASE = "https://79.137.75.155:5090";
|
||||
@@ -1,68 +1,45 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
--bg: #0f172a;
|
||||
--panel: #111827;
|
||||
--card: #1f2933;
|
||||
--messageCard: #2f3f4e;
|
||||
--border: #2d3748;
|
||||
--text: #e5e7eb;
|
||||
--muted: #9ca3af;
|
||||
--accent: #6366f1;
|
||||
--danger: #ef4444;
|
||||
--success: #22c55e;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
background-color: var(--border);
|
||||
color: var(--text);
|
||||
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 80%;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -3,5 +3,7 @@ import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
|
||||
base: './',
|
||||
plugins: [react()],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user