diff --git a/frontend/scenarios/set_type.feature b/frontend/scenarios/set_type.feature index e00214b3..bfff0070 100644 --- a/frontend/scenarios/set_type.feature +++ b/frontend/scenarios/set_type.feature @@ -24,3 +24,9 @@ Scénario: typé comme non typé Quand je choisis "remove current type" comme type de glose Alors la glose n'a pas de type +Scénario: non typé avec type non existant + + Soit un document dont je suis l'auteur affiché comme glose + Et une session active avec mon compte + Quand je qualifie le document avec un nouveau type de glose + Alors le nouveau type est le type de la glose \ No newline at end of file diff --git a/frontend/src/components/Type.jsx b/frontend/src/components/Type.jsx index 48ab3c10..69410909 100644 --- a/frontend/src/components/Type.jsx +++ b/frontend/src/components/Type.jsx @@ -2,56 +2,136 @@ import '../styles/Metadata.css'; import '../styles/Type.css'; import { TagFill } from 'react-bootstrap-icons'; -import { useState, useContext } from 'react'; -import { ListGroup, OverlayTrigger, Tooltip } from 'react-bootstrap'; +import { useState, useEffect, useContext, useCallback } from 'react'; +import { ListGroup, Form, InputGroup, Button } from 'react-bootstrap'; import { TypesContext } from './TypesContext.js'; +import { v4 as uuidv4 } from 'uuid'; export function TypeBadge({ type, addClassName }) { const types = useContext(TypesContext); if (!type) return null; const typeSelected = types.find((t) => t.id === type); - if (!typeSelected) return; - return
- {typeSelected.doc.type_name} -
; + if (!typeSelected || !typeSelected.doc) return null; + + return ( + + {typeSelected.doc.type_name} + + ); } -function TypeList({ typeSelected, handleUpdate }) { - const types = useContext(TypesContext); +function TypeList({ typeSelected, handleUpdate, addNewType, backend }) { + const [types, setTypes] = useState([]); const [searchTerm, setSearchTerm] = useState(''); + const [newType, setNewType] = useState(''); + const [newColor, setNewColor] = useState('#FF5733'); + + const fetchTypes = useCallback(async () => { + try { + const response = await backend.getView({ view: 'types', options: ['include_docs'] }); + setTypes(response); + console.log('Types récupérés:', response); + } catch (error) { + console.error('Erreur lors de la récupération des types:', error); + } + }, [backend]); + + useEffect(() => { + fetchTypes(); + }, [fetchTypes]); - const filteredTypes = types.filter(type => - type.doc.type_name.toLowerCase().includes(searchTerm.toLowerCase()) + const handleAddNewType = () => { + if (newType.trim()) { + addNewType(newType, newColor) + .then(() => { + window.location.reload(); + }) + .catch((error) => { + console.error('Erreur lors de l\'ajout du type:', error); + }); + + setNewType(''); + setNewColor('#FF5733'); + } + }; + + const filteredTypes = types.filter( + (type) => + type.doc && + type.doc.type_name && + type.doc.type_name.toLowerCase().includes(searchTerm.toLowerCase()) ); + return ( <> -
Select a type
- Select a type + setSearchTerm(e.target.value)} - style={{ marginBottom: '10px', width: '100%', padding: '5px' }} + style={{ marginBottom: '15px', padding: '10px', borderRadius: '8px' }} /> + {filteredTypes.map((type, index) => handleUpdate(type.id)}> - + onClick={() => handleUpdate(type.id)} + className="typeContainer" + style={{ cursor: 'pointer' }} + > + )} - {typeSelected ? + {typeSelected && ( handleUpdate('')}> + key="remove-type" + onClick={() => handleUpdate('')} + style={{ + color: 'red', + cursor: 'pointer', + marginTop: '10px', + textAlign: 'center' + }} + > Remove the current type - : null} + )} + +
+
Create a new type
+ + setNewType(e.target.value)} + className="inputField" + /> + + + + setNewColor(e.target.value)} + style={{ height: '40px', width: '40px', border: 'none', cursor: 'pointer' }} + /> + + +
); } @@ -77,26 +157,43 @@ function Type({ metadata, editable, backend }) { .catch(console.error); }; + const addNewType = async (newTypeName, newColor) => { + const newId = uuidv4(); + const newTypeObject = { + type_name: newTypeName, + color: newColor, + }; + + try { + const response = await backend.putDocument(newTypeObject, newId); + console.log('Type ajouté avec succès:', response); + return response; + } catch (error) { + console.error('Erreur lors de l\'ajout du type:', error); + throw new Error('L\'ajout du type a échoué. Veuillez réessayer.'); + } + }; + return (
{editable ? ( - Apply a label...} - > - - + ) : null}
- {beingEdited ? - - : null - } + {beingEdited && ( + + )}
); } diff --git a/frontend/src/styles/Type.css b/frontend/src/styles/Type.css index d683730c..ead80051 100644 --- a/frontend/src/styles/Type.css +++ b/frontend/src/styles/Type.css @@ -1,28 +1,25 @@ -.typeBadge { - margin: 10px 5px; - text-transform: capitalize; - width: fit-content; - padding: 8px 20px!important; - font-size: 12px; - font-weight: bold; - line-height: 1; - color: white; - text-align: center; - vertical-align: baseline; - border-radius: 20px; - display: inline-block; - word-break: break-word; -} - -.typeIcon { - opacity: 1 !important; - cursor: pointer; - transition: filter 0.3s ease; - display: inline-block !important; - visibility: visible !important; +.typeContainer { + background-color: white; + padding: 10px; + border-radius: 12px; + margin-bottom: 8px; /* Moins d'espace entre les badges */ + display: flex; + justify-content: center; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); } -.typeIcon:hover { - filter: brightness(1.5); +.typeBadge { + margin: 0; + text-transform: capitalize; + width: fit-content; + padding: 8px 20px !important; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: white; + text-align: center; + vertical-align: middle; + border-radius: 20px; + display: inline-block; + word-break: break-word; } - diff --git a/frontend/tests/event.js b/frontend/tests/event.js index 5adb6b2a..7fb6155e 100644 --- a/frontend/tests/event.js +++ b/frontend/tests/event.js @@ -47,6 +47,16 @@ Quand("je choisis {string} comme type de glose", (pattern) => { cy.get('.list-group-item').first().click(); }); +Quand("je qualifie le document avec un nouveau type de glose", function() { + cy.get('.typeIcon').click(); + this.randomType = [...Array(30)].map(() => Math.random().toString(36)[2]).join(''); + cy.get('.inputField.form-control').type(this.randomType); + cy.get('.addButton').click(); + cy.get('.typeIcon').click(); + cy.get('#searchType').type(this.randomType); + cy.get('.list-group-item').first().click(); +}); + Quand("je survole le texte :", (text) => { cy.contains('p[title="Highlight in document"]', text.trim()) .trigger('mouseover'); diff --git a/frontend/tests/outcome.js b/frontend/tests/outcome.js index a2c03c35..87c399da 100644 --- a/frontend/tests/outcome.js +++ b/frontend/tests/outcome.js @@ -86,6 +86,10 @@ Alors("la glose n'a pas de type", () => { cy.get('.typeSelected').should('not.exist'); }); +Alors("le nouveau type est le type de la glose", function() { + cy.contains('.typeSelected', this.randomType); +}); + Alors("le texte du document principal est en surbrillance :", (text) => { cy.contains('mark', text.trim()).should('exist'); });