Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions frontend/scenarios/set_type.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
165 changes: 131 additions & 34 deletions frontend/src/components/Type.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div style={{backgroundColor: typeSelected.doc.color}} className={`typeBadge ${addClassName ?? ''}`}>
{typeSelected.doc.type_name}
</div>;
if (!typeSelected || !typeSelected.doc) return null;

return (
<span
className={`typeBadge ${addClassName ?? ''}`}
style={{ backgroundColor: typeSelected.doc.color }}
>
{typeSelected.doc.type_name}
</span>
);
}

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 (
<>
<h6 style={{ textAlign: 'left' }}>Select a type</h6>
<input
<h6 style={{ textAlign: 'left', marginBottom: '15px' }}>Select a type</h6>
<Form.Control
type="text"
id="searchType"
placeholder="Filter types..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{ marginBottom: '10px', width: '100%', padding: '5px' }}
style={{ marginBottom: '15px', padding: '10px', borderRadius: '8px' }}
/>

<ListGroup style={{ textAlign: 'center', paddingTop: 0, paddingBottom: 20 }}>
{filteredTypes.map((type, index) =>
<ListGroup.Item action
key={index}
style={{ backgroundColor: type === typeSelected ? 'grey' : '' }}
onClick={() => handleUpdate(type.id)}>
<TypeBadge type={type.id}/>
onClick={() => handleUpdate(type.id)}
className="typeContainer"
style={{ cursor: 'pointer' }}
>
<TypeBadge type={type.id} />
</ListGroup.Item>
)}
{typeSelected ?
{typeSelected && (
<ListGroup.Item action
key={'remove type'}
style={{ color: 'red' }}
onClick={() => handleUpdate('')}>
key="remove-type"
onClick={() => handleUpdate('')}
style={{
color: 'red',
cursor: 'pointer',
marginTop: '10px',
textAlign: 'center'
}}
>
Remove the current type
</ListGroup.Item>
: null}
)}
</ListGroup>

<div style={{ marginTop: '20px' }}>
<h6 style={{ textAlign: 'left', marginBottom: '10px' }}>Create a new type</h6>
<InputGroup style={{ marginBottom: '10px' }}>
<Form.Control
type="text"
placeholder="New type name"
value={newType}
onChange={(e) => setNewType(e.target.value)}
className="inputField"
/>
</InputGroup>

<InputGroup>
<Form.Control
type="color"
value={newColor}
onChange={(e) => setNewColor(e.target.value)}
style={{ height: '40px', width: '40px', border: 'none', cursor: 'pointer' }}
/>
<Button
variant="success"
onClick={handleAddNewType}
className="addButton"
>
Add Type
</Button>
</InputGroup>
</div>
</>
);
}
Expand All @@ -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 (
<div style={{ paddingTop: 10, paddingBottom: 30 }}>
<div style={{ paddingTop: 0, justifyContent: 'flex-end' }}>
<TypeBadge addClassName="typeSelected" type={typeSelected}/>
{editable ? (
<OverlayTrigger
placement="top"
overlay={<Tooltip id="tooltip-apply-label">Apply a label...</Tooltip>}
>
<TagFill
onClick={handleEdit}
className="icon typeIcon always-visible"
/>
</OverlayTrigger>
<TagFill
onClick={handleEdit}
className="icon typeIcon"
title="Apply a label..."
/>
) : null}
</div>
{beingEdited ?
<TypeList typeSelected={typeSelected} handleUpdate={handleUpdate}/>
: null
}
{beingEdited && (
<TypeList
typeSelected={typeSelected}
handleUpdate={handleUpdate}
addNewType={addNewType}
backend={backend}
/>
)}
</div>
);
}
Expand Down
47 changes: 22 additions & 25 deletions frontend/src/styles/Type.css
Original file line number Diff line number Diff line change
@@ -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;
}

10 changes: 10 additions & 0 deletions frontend/tests/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
4 changes: 4 additions & 0 deletions frontend/tests/outcome.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down