interface interactive
This commit is contained in:
@@ -1,199 +1,50 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export const patterns = [
|
||||
{
|
||||
regex: /<PERSON>/g,
|
||||
className: "bg-blue-200 text-blue-800",
|
||||
label: "Personne",
|
||||
},
|
||||
{
|
||||
regex: /<EMAIL_ADDRESS>/g,
|
||||
className: "bg-green-200 text-green-800",
|
||||
label: "Adresse Email",
|
||||
},
|
||||
{
|
||||
regex: /<PHONE_NUMBER>/g,
|
||||
className: "bg-purple-200 text-purple-800",
|
||||
label: "N° de Téléphone",
|
||||
},
|
||||
{
|
||||
regex: /<LOCATION>/g,
|
||||
className: "bg-red-200 text-red-800",
|
||||
label: "Lieu",
|
||||
},
|
||||
{
|
||||
regex: /<IBAN>/g,
|
||||
className: "bg-yellow-200 text-yellow-800",
|
||||
label: "IBAN",
|
||||
},
|
||||
{
|
||||
regex: /<ORGANIZATION>/g,
|
||||
className: "bg-indigo-200 text-indigo-800",
|
||||
label: "Organisation",
|
||||
},
|
||||
{
|
||||
regex: /<FLEXIBLE_DATE>/g,
|
||||
className: "bg-pink-200 text-pink-800",
|
||||
label: "Date",
|
||||
},
|
||||
{
|
||||
regex: /<BE_ADDRESS>/g,
|
||||
className: "bg-cyan-200 text-cyan-800",
|
||||
label: "Adresse (BE)",
|
||||
},
|
||||
{
|
||||
regex: /<BE_PHONE_NUMBER>/g,
|
||||
className: "bg-violet-200 text-violet-800",
|
||||
label: "N° de Tél. (BE)",
|
||||
},
|
||||
{
|
||||
regex: /<CREDIT_CARD>/g,
|
||||
className: "bg-orange-200 text-orange-800",
|
||||
label: "Carte de Crédit",
|
||||
},
|
||||
{
|
||||
regex: /<URL>/g,
|
||||
className: "bg-teal-200 text-teal-800",
|
||||
label: "URL",
|
||||
},
|
||||
{
|
||||
regex: /<IP_ADDRESS>/g,
|
||||
className: "bg-gray-300 text-gray-900",
|
||||
label: "Adresse IP",
|
||||
},
|
||||
{
|
||||
regex: /<DATE_TIME>/g,
|
||||
className: "bg-pink-300 text-pink-900",
|
||||
label: "Date & Heure",
|
||||
},
|
||||
{
|
||||
regex: /<NRP>/g,
|
||||
className: "bg-red-300 text-red-900",
|
||||
label: "N° Registre National",
|
||||
},
|
||||
{
|
||||
regex: /<BE_VAT>/g,
|
||||
className: "bg-yellow-300 text-yellow-900",
|
||||
label: "TVA (BE)",
|
||||
},
|
||||
{
|
||||
regex: /<BE_ENTERPRISE_NUMBER>/g,
|
||||
className: "bg-lime-200 text-lime-800",
|
||||
label: "N° d'entreprise (BE)",
|
||||
},
|
||||
{
|
||||
regex: /<BE_PRO_ID>/g,
|
||||
className: "bg-emerald-200 text-emerald-800",
|
||||
label: "ID Pro (BE)",
|
||||
},
|
||||
];
|
||||
|
||||
interface EntityMapping {
|
||||
originalValue: string;
|
||||
anonymizedValue: string;
|
||||
entityType: string;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
}
|
||||
import React, { ReactNode } from "react";
|
||||
import {
|
||||
generateColorFromName,
|
||||
EntityMapping,
|
||||
} from "@/app/config/entityLabels";
|
||||
|
||||
export const highlightEntities = (
|
||||
text: string,
|
||||
entityMappings?: EntityMapping[]
|
||||
): ReactNode => {
|
||||
if (!text) return text;
|
||||
|
||||
const replacements: Array<{
|
||||
start: number;
|
||||
end: number;
|
||||
element: ReactNode;
|
||||
}> = [];
|
||||
|
||||
// Trouver toutes les correspondances
|
||||
patterns.forEach((pattern, patternIndex) => {
|
||||
const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
|
||||
let match;
|
||||
let matchCount = 0; // Compteur pour ce type d'entité
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const start = match.index;
|
||||
const end = match.index + match[0].length;
|
||||
|
||||
// Vérifier qu'il n'y a pas de chevauchement avec des remplacements existants
|
||||
const hasOverlap = replacements.some(
|
||||
(r) =>
|
||||
(start >= r.start && start < r.end) || (end > r.start && end <= r.end)
|
||||
);
|
||||
|
||||
if (!hasOverlap) {
|
||||
matchCount++; // Incrémenter le compteur pour ce type
|
||||
let displayLabel = pattern.label;
|
||||
const displayClass = pattern.className;
|
||||
|
||||
if (entityMappings) {
|
||||
// Chercher le mapping correspondant à cette position et ce type
|
||||
const matchingMapping = entityMappings.find(
|
||||
(mapping) => mapping.entityType === pattern.label
|
||||
);
|
||||
|
||||
if (matchingMapping) {
|
||||
// Utiliser directement la valeur anonymisée du mapping
|
||||
// qui correspond à cette occurrence (basée sur l'ordre d'apparition)
|
||||
const entityType = pattern.label;
|
||||
const mappingsOfThisType = entityMappings.filter(
|
||||
(m) => m.entityType === entityType
|
||||
);
|
||||
|
||||
// Prendre le mapping correspondant à cette occurrence
|
||||
if (mappingsOfThisType[matchCount - 1]) {
|
||||
displayLabel = mappingsOfThisType[matchCount - 1].anonymizedValue;
|
||||
} else {
|
||||
// Fallback si pas de mapping trouvé
|
||||
displayLabel = `${entityType} [${matchCount}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const element = (
|
||||
<span
|
||||
key={`${patternIndex}-${start}`}
|
||||
className={`${displayClass} px-2 py-1 rounded-md font-medium text-xs inline-block mx-0.5 shadow-sm border`}
|
||||
title={`${displayLabel} anonymisé`}
|
||||
>
|
||||
{displayLabel}
|
||||
</span>
|
||||
);
|
||||
|
||||
replacements.push({ start, end, element });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Trier les remplacements par position
|
||||
replacements.sort((a, b) => a.start - b.start);
|
||||
|
||||
// Construire le résultat final
|
||||
if (replacements.length === 0) {
|
||||
return text;
|
||||
originalText: string,
|
||||
mappings?: EntityMapping[]
|
||||
): ReactNode[] => {
|
||||
if (!originalText || !mappings || mappings.length === 0) {
|
||||
return [originalText];
|
||||
}
|
||||
|
||||
const parts: ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
replacements.forEach((replacement) => {
|
||||
// Ajouter le texte avant le remplacement
|
||||
if (replacement.start > lastIndex) {
|
||||
parts.push(text.slice(lastIndex, replacement.start));
|
||||
// Les mappings sont triés par `start`
|
||||
mappings.forEach((mapping, index) => {
|
||||
const { start, end, entity_type, text } = mapping;
|
||||
|
||||
// Ajouter le segment de texte AVANT l'entité actuelle
|
||||
if (start > lastIndex) {
|
||||
parts.push(originalText.slice(lastIndex, start));
|
||||
}
|
||||
|
||||
// Ajouter l'élément de remplacement
|
||||
parts.push(replacement.element);
|
||||
// Créer et ajouter le badge stylisé pour l'entité
|
||||
const colorOption = generateColorFromName(entity_type);
|
||||
const displayText = mapping.displayName || `[${entity_type.toUpperCase()}]`;
|
||||
|
||||
parts.push(
|
||||
<span
|
||||
key={index}
|
||||
className={`${colorOption.bgClass} ${colorOption.textClass} px-2 py-1 rounded-md font-medium text-xs inline-block mx-0.5 shadow-sm border`}
|
||||
title={`${entity_type}: ${text}`}
|
||||
>
|
||||
{displayText}
|
||||
</span>
|
||||
);
|
||||
|
||||
lastIndex = replacement.end;
|
||||
// Mettre à jour la position pour la prochaine itération
|
||||
lastIndex = end;
|
||||
});
|
||||
|
||||
// Ajouter le texte restant
|
||||
if (lastIndex < text.length) {
|
||||
parts.push(text.slice(lastIndex));
|
||||
// Ajouter le reste du texte après la dernière entité
|
||||
if (lastIndex < originalText.length) {
|
||||
parts.push(originalText.slice(lastIndex));
|
||||
}
|
||||
|
||||
return parts;
|
||||
|
||||
Reference in New Issue
Block a user