224 lines
5.7 KiB
TypeScript
224 lines
5.7 KiB
TypeScript
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;
|
|
}
|
|
|
|
export const highlightEntities = (
|
|
text: string,
|
|
entityMappings?: EntityMapping[]
|
|
): ReactNode => {
|
|
if (!text) return text;
|
|
|
|
const replacements: Array<{
|
|
start: number;
|
|
end: number;
|
|
element: ReactNode;
|
|
}> = [];
|
|
|
|
// Si on a des mappings, on les utilise pour créer un mapping des valeurs anonymisées
|
|
const anonymizedValueMap = new Map<
|
|
string,
|
|
{ label: string; className: string }
|
|
>();
|
|
|
|
if (entityMappings) {
|
|
entityMappings.forEach((mapping) => {
|
|
// Trouver le pattern correspondant au type d'entité
|
|
const pattern = patterns.find((p) => p.label === mapping.entityType);
|
|
if (pattern) {
|
|
anonymizedValueMap.set(mapping.anonymizedValue, {
|
|
label: mapping.anonymizedValue,
|
|
className: pattern.className,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Trouver toutes les correspondances
|
|
patterns.forEach((pattern, patternIndex) => {
|
|
const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
|
|
let match;
|
|
|
|
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) {
|
|
// Chercher si on a un mapping pour cette entité
|
|
let displayLabel = pattern.label;
|
|
const displayClass = pattern.className; // Changé de 'let' à 'const'
|
|
|
|
if (entityMappings) {
|
|
// Compter les occurrences précédentes du même type pour déterminer le numéro
|
|
const entityType = pattern.label;
|
|
const previousMatches = replacements.filter((r) => {
|
|
// Correction du type 'any' en utilisant une interface plus spécifique
|
|
const element = r.element as React.ReactElement<{
|
|
className?: string;
|
|
}>;
|
|
const prevPattern = patterns.find(
|
|
(p) =>
|
|
p.className ===
|
|
element?.props?.className?.split(" ")[0] +
|
|
" " +
|
|
element?.props?.className?.split(" ")[1]
|
|
);
|
|
return prevPattern?.label === entityType;
|
|
}).length;
|
|
|
|
const matchingMapping = entityMappings.find(
|
|
(mapping) => mapping.entityType === entityType
|
|
);
|
|
|
|
if (matchingMapping) {
|
|
// Utiliser le compteur pour déterminer le bon numéro avec des crochets
|
|
const entityCount = previousMatches + 1;
|
|
displayLabel = `${entityType} [${entityCount}]`;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
// Ajouter l'élément de remplacement
|
|
parts.push(replacement.element);
|
|
|
|
lastIndex = replacement.end;
|
|
});
|
|
|
|
// Ajouter le texte restant
|
|
if (lastIndex < text.length) {
|
|
parts.push(text.slice(lastIndex));
|
|
}
|
|
|
|
return parts;
|
|
};
|