Repository: Arquisoft/wichat_es3b
Pull request #133: Chatbox fix frontend
Author: login: UO291047
Created: 2025-03-23T13:03:34Z | Merged: 2025-03-23T15:57:49Z



Diff:
diff --git a/llmservice/llm-service.test.js b/llmservice/llm-service.test.js
index 3fdc4f06ba..c8552da26e 100644
--- a/llmservice/llm-service.test.js
+++ b/llmservice/llm-service.test.js
@@ -56,11 +56,11 @@ describe("LLM Service", () => {
   // Test del endpoint /ask con los parámetros correctos
   it("should process ask request with valid parameters", async () => {
     const testQuestion = {
-      respuestaCorrecta: "El Big Ben",
+      respuestaCorrecta: 'El Big Ben',
       pregunta: {
-        es: "¿Cuál es este monumento?",
-        en: "What is this monument?",
-        idioma: "¿Cuál es este monumento?",
+        es: '¿Cuál es este monumento?',
+        en: 'What is this monument?',
+        idioma: '¿Cuál es este monumento?'
       },
       descripcion: [
         { propiedad: "Capital", valor: "Londres" },
diff --git a/webapp/src/components/chatBox/ChatBox.css b/webapp/src/components/chatBox/ChatBox.css
index 9c7573edfc..e1571e407b 100644
--- a/webapp/src/components/chatBox/ChatBox.css
+++ b/webapp/src/components/chatBox/ChatBox.css
@@ -1,11 +1,9 @@
 @import url("../../assets/global.css");
 
 .chat-box {
-  width: 90%;
-  max-width: 350px;
-  border: 2px solid var(--color-primario);
+  height: 100%;
   border-radius: 0.625rem;
-  padding: 1rem;
+  padding: 0.5rem;
   background-color: #ffffff;
   display: flex;
   flex-direction: column;
@@ -13,11 +11,12 @@
 }
 
 .messages {
+  flex-grow: 1;
   display: flex;
   flex-direction: column;
   gap: 0.5rem;
-  max-height: 40vh;
   overflow-y: auto;
+  padding: 0.5rem;
 }
 
 .input-container {
@@ -36,19 +35,59 @@
   font-size: 1rem;
 }
 
-#sendMessageToAIButton {
+.send-button {
   background-color: var(--color-primario);
-  padding: 0.5em;
+  border: none;
   border-radius: 50%;
-  color: white;
-}
-
-#sendMessageToAIButton:hover {
+  width: 2.5rem;
+  height: 2.5rem;
+  display: flex;
+  justify-content: center;
+  align-items: center;
   cursor: pointer;
-  background-color: var(--color-hover-boton);
-  transition: 0.3s ease-in-out;
+  transition: background-color 0.3s ease;
 }
 
 .send-button:hover {
   background-color: var(--color-hover-boton);
 }
+
+#sendMessageToAIButton {
+  color: white;
+  font-size: 1.5rem;
+}
+
+/* Responsive styles */
+@media (max-width: 768px) {
+  .chat-box {
+    padding: 0.75rem;
+  }
+
+  .input-container input {
+    padding: 0.4rem 0.8rem;
+    font-size: 0.9rem;
+  }
+
+  .send-button {
+    width: 2rem;
+    height: 2rem;
+  }
+
+  #sendMessageToAIButton {
+    font-size: 1.2rem;
+  }
+}
+
+@media (max-width: 480px) {
+  .chat-box {
+    padding: 0.5rem;
+  }
+
+  .messages {
+    padding: 0.3rem;
+  }
+
+  .input-container {
+    gap: 0.4rem;
+  }
+}
\ No newline at end of file
diff --git a/webapp/src/components/chatBox/ChatBox.js b/webapp/src/components/chatBox/ChatBox.js
index 080de77057..3c25aff274 100644
--- a/webapp/src/components/chatBox/ChatBox.js
+++ b/webapp/src/components/chatBox/ChatBox.js
@@ -4,7 +4,7 @@ import "./ChatBox.css";
 import ChatBubble from "../chatBubble/ChatBubble";
 import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
 
-const ChatBox = ({ question, language = "es" }) => {
+const ChatBox = ({ question, language = "es", isVisible }) => {
   const [messages, setMessages] = useState([]);
   const [hint, setHint] = useState("");
   const [isLoadingHint, setIsLoadingHint] = useState(false);
@@ -63,6 +63,12 @@ const ChatBox = ({ question, language = "es" }) => {
     getHint();
   };
 
+  const handleKeyPress = (e) => {
+    if (e.key === 'Enter') {
+      handleSend();
+    }
+  };
+
   useEffect(() => {
     let interval;
     if (isLoadingHint) {
@@ -77,32 +83,36 @@ const ChatBox = ({ question, language = "es" }) => {
     return () => clearInterval(interval);
   }, [isLoadingHint]);
 
+  if (!isVisible) return null;
+
   return (
-    <div className="chat-box">
-      <div className="messages">
-        {messages.map((msg, index) => (
-          <ChatBubble key={index} message={msg.text} isSender={msg.isSender} />
-        ))}
-        {isLoadingHint && (
-          <ChatBubble message={loadingMessage} isSender={false} />
-        )}
-      </div>
-      <div className="input-container">
-        <input
-          type="text"
-          value={input}
-          onChange={(e) => setInput(e.target.value)}
-          placeholder="Pídele una pista a la IA..."
-        />
-        <ArrowUpwardIcon
-          titleAccess="Envía un mensaje"
-          fontSize="1.5em"
-          id="sendMessageToAIButton"
-          onClick={handleSend}
-        />
+      <div className="chat-box">
+        <div className="messages">
+          {messages.map((msg, index) => (
+              <ChatBubble key={index} message={msg.text} isSender={msg.isSender} />
+          ))}
+          {isLoadingHint && (
+              <ChatBubble message={loadingMessage} isSender={false} />
+          )}
+        </div>
+        <div className="input-container">
+          <input
+              type="text"
+              value={input}
+              onChange={(e) => setInput(e.target.value)}
+              placeholder="Pídele una pista a la IA..."
+              onKeyPress={handleKeyPress}
+          />
+          <button className="send-button" onClick={handleSend}>
+            <ArrowUpwardIcon
+                titleAccess="Envía un mensaje"
+                fontSize="inherit"
+                id="sendMessageToAIButton"
+            />
+          </button>
+        </div>
       </div>
-    </div>
   );
 };
 
-export default ChatBox;
+export default ChatBox;
\ No newline at end of file
diff --git a/webapp/src/components/chatBubble/ChatBubble.css b/webapp/src/components/chatBubble/ChatBubble.css
index d7e0c2f7bc..a82f157d75 100644
--- a/webapp/src/components/chatBubble/ChatBubble.css
+++ b/webapp/src/components/chatBubble/ChatBubble.css
@@ -12,12 +12,13 @@
   .chat-container.received {
     justify-content: flex-start;
   }
-  
+
   .chat-bubble {
     max-width: 70%;
     padding: 12px;
     border-radius: 16px;
     box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
+    margin-bottom: 0.5em;
   }
   
   .chat-bubble.sent {
diff --git a/webapp/src/pages/game/Game.css b/webapp/src/pages/game/Game.css
index e5fb66ac7b..cf59608d7d 100644
--- a/webapp/src/pages/game/Game.css
+++ b/webapp/src/pages/game/Game.css
@@ -6,8 +6,8 @@
   left: 2rem;
   top: 50%;
   transform: translateY(-50%);
-  width: 330px;
-  height: 430px;
+  width: 21rem;
+  height: 28rem;
   background-color: white;
   border-radius: 10px;
   box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
@@ -15,6 +15,19 @@
   overflow: hidden;
   display: flex;
   flex-direction: column;
+  border: 2px solid var(--color-primario);
+  transition: transform 0.3s ease, opacity 0.3s ease;
+}
+
+.chatBoxContainer.hidden {
+  transform: translateX(-110%) translateY(-50%);
+  opacity: 0;
+  pointer-events: none;
+}
+
+.chatBoxContainer.visible {
+  transform: translateX(0) translateY(-50%);
+  opacity: 1;
 }
 
 /* Estilos para el componente chat-box */
@@ -22,7 +35,6 @@
   display: flex;
   flex-direction: column;
   height: 100%;
-  width: 100%;
 }
 
 /* Contenedor de mensajes con scroll */
@@ -72,23 +84,77 @@
 /* Estilo para el botón de enviar */
 #sendMessageToAIButton {
   color: white;
+}
+
+.send-button {
   background-color: var(--color-primario);
   cursor: pointer;
   padding: 0.3rem;
   border-radius: 50%;
+  border: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
   transition: background-color 0.2s;
+  width: 2.5rem;
+  height: 2.5rem;
 }
 
-#sendMessageToAIButton:hover {
+.send-button:hover {
   background-color: var(--color-hover-boton);
 }
 
 /* Responsividad para dispositivos móviles */
+@media (max-width: 1200px) {
+  .chatBoxContainer {
+    width: 18rem;
+    height: 25rem;
+  }
+}
+
+@media (max-width: 992px) {
+  .chatBoxContainer {
+    width: 16rem;
+    height: 24rem;
+    left: 1.5rem;
+  }
+}
+
 @media (max-width: 768px) {
   .chatBoxContainer {
+    width: 15rem;
+    height: 22rem;
     left: 1rem;
-    width: 250px;
-    height: 350px;
+  }
+
+  .input-container input {
+    padding: 0.4rem 0.8rem;
+  }
+
+  .send-button {
+    width: 2.2rem;
+    height: 2.2rem;
+  }
+}
+
+@media (max-width: 576px) {
+  .chatBoxContainer {
+    width: 14rem;
+    height: 20rem;
+    left: 0.5rem;
+  }
+
+  .messages {
+    padding: 0.75rem;
+  }
+
+  .input-container {
+    padding: 0.5rem;
+  }
+
+  .send-button {
+    width: 2rem;
+    height: 2rem;
   }
 }
 
@@ -180,15 +246,6 @@
   box-shadow: 4px 4px 4px rgba(0,0,0,0.1)
 }
 
-/* .midSection img {
-  position: relative;
-  border: 2px solid var(--color-primario);
-  border-radius: 5px;
-  height: 50%;
-  width: 50%;
-  object-fit: cover;
-} */
-
 .answerPanel {
   display: grid;
   grid-template-columns: 1fr 1fr;
@@ -264,4 +321,4 @@ main {
     transform: scale(0.75);
     transform-origin: top;
   }
-}
+}
\ No newline at end of file
diff --git a/webapp/src/pages/game/Game.js b/webapp/src/pages/game/Game.js
index 00f8a81868..beec1867f1 100644
--- a/webapp/src/pages/game/Game.js
+++ b/webapp/src/pages/game/Game.js
@@ -22,6 +22,7 @@ const Game = () => {
   const [isAnswered, setIsAnswered] = useState(false);
   const [timeLeft, setTimeLeft] = useState(TOTAL_TIME);
   const [progress, setProgress] = useState(100);
+  const [isChatBoxVisible, setIsChatBoxVisible] = useState(false);
 
   const timerRef = useRef(null); // Referencia para almacenar el ID del intervalo
 
@@ -93,8 +94,12 @@ const Game = () => {
     const minutes = Math.floor(time / 60);
     const seconds = time % 60;
     return `${minutes.toString().padStart(2, "0")}:${seconds
-      .toString()
-      .padStart(2, "0")}`;
+        .toString()
+        .padStart(2, "0")}`;
+  };
+
+  const toggleChatBox = () => {
+    setIsChatBoxVisible(!isChatBoxVisible);
   };
 
   if (isLoading) {
@@ -102,93 +107,92 @@ const Game = () => {
   }
 
   return (
-    <div>
-      <Nav />
-      <main>
-        <div className="upperSection">
-          <div className="hintButton">
-            <HintButton
-              text={"¿Necesitas una pista?"}
-              onClick={() => alert("Tienes una pista!")}
-            />
-          </div>
-          <div className="question">
-            <div className="questionNumber">
-              <h2>{`Pregunta ${questionNumber + 1}/25`}</h2>
-              <ArrowForwardIcon
-                titleAccess="Siguiente pregunta"
-                fontSize="1.5em"
-                id="nextArrow"
-                onClick={isAnswered ? handleNextQuestion : null}
-                style={{
-                  color: isAnswered ? "white" : "gray",
-                  cursor: isAnswered ? "pointer" : "not-allowed",
-                }}
-              ></ArrowForwardIcon>
+      <div>
+        <Nav />
+        <main>
+          <div className="upperSection">
+            <div className="hintButton">
+              <HintButton
+                  text={isChatBoxVisible ? "Ocultar pistas" : "¿Necesitas una pista?"}
+                  onClick={toggleChatBox}
+              />
             </div>
-            <h1>{currentQuestion.pregunta}</h1>
-          </div>
-          <div className="rightUpperSection">
-            <div className="pointsAndRules">
-              <div>
-                <span>Puntuación: </span> <span className="score">{score}</span>
+            <div className="question">
+              <div className="questionNumber">
+                <h2>{`Pregunta ${questionNumber + 1}/25`}</h2>
+                <ArrowForwardIcon
+                    titleAccess="Siguiente pregunta"
+                    fontSize="1.5em"
+                    id="nextArrow"
+                    onClick={isAnswered ? handleNextQuestion : null}
+                    style={{
+                      color: isAnswered ? "white" : "gray",
+                      cursor: isAnswered ? "pointer" : "not-allowed",
+                    }}
+                ></ArrowForwardIcon>
+              </div>
+              <h1>{currentQuestion.pregunta}</h1>
+            </div>
+            <div className="rightUpperSection">
+              <div className="pointsAndRules">
+                <div>
+                  <span>Puntuación: </span> <span className="score">{score}</span>
+                </div>
+                <BaseButton text={"Reglas"} buttonType="buttonSecondary" />
               </div>
-              <BaseButton text={"Reglas"} buttonType="buttonSecondary" />
             </div>
           </div>
-        </div>
-        <div className="midSection">
-          {/* <div className="questionImageContainer"> */}
-          {currentQuestion.img && (
-            <img src={currentQuestion.img[0]} alt="imagen pregunta"></img>
-          )}
-          <div className="answerPanel">
-            {currentQuestion.respuestas &&
-              currentQuestion.respuestas.map((respuesta, index) => (
-                <BaseButton
-                  key={index}
-                  text={respuesta}
-                  onClick={() => handleAnswerClick(respuesta)}
-                  buttonType={
-                    isAnswered
-                      ? respuesta === currentQuestion.respuestaCorrecta
-                        ? "buttonCorrect"
-                        : selectedAnswer === respuesta
-                        ? "buttonIncorrect"
-                        : "buttonPrimary"
-                      : "buttonPrimary"
-                  }
-                  disabled={isAnswered}
-                ></BaseButton>
-              ))}
+          <div className="midSection">
+            {currentQuestion.img && (
+                <img src={currentQuestion.img[0]} alt="imagen pregunta"></img>
+            )}
+            <div className="answerPanel">
+              {currentQuestion.respuestas &&
+                  currentQuestion.respuestas.map((respuesta, index) => (
+                      <BaseButton
+                          key={index}
+                          text={respuesta}
+                          onClick={() => handleAnswerClick(respuesta)}
+                          buttonType={
+                            isAnswered
+                                ? respuesta === currentQuestion.respuestaCorrecta
+                                    ? "buttonCorrect"
+                                    : selectedAnswer === respuesta
+                                        ? "buttonIncorrect"
+                                        : "buttonPrimary"
+                                : "buttonPrimary"
+                          }
+                          disabled={isAnswered}
+                      ></BaseButton>
+                  ))}
+            </div>
           </div>
-        </div>
-        <div className="lowerSection">
-          <Box
-            display="flex"
-            alignItems="center"
-            width="50%"
-            margin="auto"
-            gap={2}
-          >
-            <span>Tiempo</span>
-            <Box width="100%" position="relative">
-              <LinearProgress
-                id="progressBar"
-                variant="determinate"
-                value={progress}
-              ></LinearProgress>
+          <div className="lowerSection">
+            <Box
+                display="flex"
+                alignItems="center"
+                width="50%"
+                margin="auto"
+                gap={2}
+            >
+              <span>Tiempo</span>
+              <Box width="100%" position="relative">
+                <LinearProgress
+                    id="progressBar"
+                    variant="determinate"
+                    value={progress}
+                ></LinearProgress>
+              </Box>
+              <span>{formatTime(timeLeft)}</span>
             </Box>
-            <span>{formatTime(timeLeft)}</span>
-          </Box>
-        </div>
-        <div></div>
-        <div className="chatBoxContainer">
-          <ChatBox question={currentQuestion} language="es" />
-        </div>
-      </main>
-      <Footer />
-    </div>
+          </div>
+          <div></div>
+          <div className={`chatBoxContainer ${isChatBoxVisible ? 'visible' : 'hidden'}`}>
+            <ChatBox question={currentQuestion} language="es" isVisible={true} />
+          </div>
+        </main>
+        <Footer />
+      </div>
   );
 };
-export default Game;
+export default Game;
\ No newline at end of file


Reviews:
user: login: alejandroVG2305
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-23T15:17:18Z
user: login: alejandroVG2305
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-23T15:17:52Z

Review comments:
diff_hunk: @@ -1,9 +1,8 @@
 @import url("../../assets/global.css");
 
 .chat-box {
-  width: 90%;
-  max-width: 350px;
-  border: 2px solid var(--color-primario);
+  width: 100%;
path: webapp/src/components/chatBox/ChatBox.css
user: login: alejandroVG2305
body: Eliminar este width:100%
created_at: 2025-03-23T15:17:17Z
author_association: NONE
diff_hunk: @@ -1,9 +1,8 @@
 @import url("../../assets/global.css");
 
 .chat-box {
-  width: 90%;
-  max-width: 350px;
-  border: 2px solid var(--color-primario);
+  width: 100%;
+  height: 100%;
   border-radius: 0.625rem;
   padding: 1rem;
path: webapp/src/components/chatBox/ChatBox.css
user: login: alejandroVG2305
body: Reducir este padding a 0.5rem
created_at: 2025-03-23T15:17:51Z
author_association: NONE

PR comments:
user: login: alejandroVG2305
created_at: 2025-03-23T15:16:34Z
body: Lo veo bastante bien. El único problema es que el chatbox está un poco desplazado hacia la derecha. Hay que tocar únicamente dos propiedades css en .chatbox
author_association: NONE
user: login: alejandroVG2305
created_at: 2025-03-23T15:18:24Z
body: Con los cambios indicados debería quedar ya bien encajado
author_association: NONE
user: login: alejandroVG2305
created_at: 2025-03-23T15:30:32Z
body: Eliminar aquí también el width:100% en el .chatbox de Game.css
![image](https://github.com/user-attachments/assets/1e876863-b402-4e43-9e6c-42afbac587de)

author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-03-23T15:47:06Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=Arquisoft_wichat_es3b&pullRequest=133) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [18.9% Coverage on New Code](https://sonarcloud.io/component_measures?id=Arquisoft_wichat_es3b&pullRequest=133&metric=new_coverage&view=list) (required ≥ 80%)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [C Reliability Rating on New Code](https://sonarcloud.io/dashboard?id=Arquisoft_wichat_es3b&pullRequest=133) (required ≥ A)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Arquisoft_wichat_es3b&pullRequest=133)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


author_association: NONE