Skip to content
Closed
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
36 changes: 20 additions & 16 deletions frontend/scenarios/set_type.feature
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
#language: fr

Fonctionnalité: Qualifier un document

Scénario: non typé avec un type pré-existant
Scénario: non typé avec un type pré-existant

Soit un document dont je suis l'auteur affiché comme glose
Et une session active avec mon compte
Quand je choisis "interview" comme type de glose
Alors le type "Ethnography/Interview" est le type de la glose
Soit un document dont je suis l'auteur affiché comme glose
Et une session active avec mon compte
Quand je choisis "interview" comme type de glose
Alors le type "Ethnography/Interview" est le type de la glose

Scénario: typé avec un autre type pré-existant
Scénario: typé avec un autre type pré-existant

Soit un document dont je suis l'auteur affiché comme glose et dont le type est "Ethnography/Report"
Et une session active avec mon compte
Quand je choisis "interview" comme type de glose
Alors le type "Ethnography/Interview" est le type de la glose
Soit un document dont je suis l'auteur affiché comme glose et dont le type est "Ethnography/Report"
Et une session active avec mon compte
Quand je choisis "interview" comme type de glose
Alors le type "Ethnography/Interview" est le type de la glose

Scénario: typé comme non typé

Scénario: typé comme non typé
Soit un document dont je suis l'auteur affiché comme glose et dont le type est "Ethnography/Interview"
Et une session active avec mon compte
Quand je choisis "remove current type" comme type de glose
Alors la glose n'a pas de type

Soit un document dont je suis l'auteur affiché comme glose et dont le type est "Ethnography/Interview"
Et une session active avec mon compte
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
195 changes: 145 additions & 50 deletions frontend/src/components/Type.jsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,144 @@
import '../styles/Metadata.css';
import '../styles/Type.css';

import { useState, useEffect, useContext } from 'react';
import { TagFill } from 'react-bootstrap-icons';
import { useState, useContext } from 'react';
import { ListGroup, OverlayTrigger, Tooltip } from 'react-bootstrap';
import { ListGroup, Form, InputGroup, Button } from 'react-bootstrap';
import { TypesContext } from './TypesContext.js';
import { v4 as uuidv4 } from 'uuid';

import '../styles/Metadata.css';
import '../styles/Type.css';

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 = 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);
}
};

useEffect(() => {
fetchTypes();
}, []);

Check warning on line 44 in frontend/src/components/Type.jsx

View workflow job for this annotation

GitHub Actions / build / build

React Hook useEffect has a missing dependency: 'fetchTypes'. Either include it or remove the dependency array

Check warning on line 44 in frontend/src/components/Type.jsx

View workflow job for this annotation

GitHub Actions / build / build

React Hook useEffect has a missing dependency: 'fetchTypes'. Either include it or remove the dependency array

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_name.toLowerCase().includes(searchTerm.toLowerCase())
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
{filteredTypes.map((type, index) => (
<div
key={index}
style={{ backgroundColor: type === typeSelected ? 'grey' : '' }}
onClick={() => handleUpdate(type.id)}>
<TypeBadge type={type.id}/>
</ListGroup.Item>
)}
{typeSelected ?
<ListGroup.Item action
key={'remove type'}
style={{ color: 'red' }}
onClick={() => handleUpdate('')}>
onClick={() => handleUpdate(type.id)}
className="typeContainer"
style={{ cursor: 'pointer' }}
>
<TypeBadge type={type.id} />
</div>
))}
{typeSelected && (
<div
key="remove-type"
onClick={() => handleUpdate('')}
style={{
color: 'red',
cursor: 'pointer',
marginTop: '10px',
textAlign: 'center'
}}
>
Remove the current type
</ListGroup.Item>
: null}
</div>
)}
</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>
</>
);
}

function Type({ metadata, editable, backend }) {
const [ beingEdited, setBeingEdited ] = useState(false);
const [ typeSelected, setTypeSelected ] = useState(metadata.type);
const [ editedDocument, setEditedDocument ] = useState(metadata);
const [beingEdited, setBeingEdited] = useState(false);
const [typeSelected, setTypeSelected] = useState(metadata.type);
const [editedDocument, setEditedDocument] = useState(metadata);

const handleEdit = () => {
setBeingEdited(!beingEdited);
Expand All @@ -73,30 +152,46 @@
const handleUpdate = async (type) => {
setTypeSelected(type);
setBeingEdited(false);
await backend.putDocument({ ...editedDocument, type })
.catch(console.error);
await backend.putDocument({ ...editedDocument, type }).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}/>
<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;
}

16 changes: 16 additions & 0 deletions node_modules/.bin/uuid

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions node_modules/.bin/uuid.cmd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading