interface interactive
This commit is contained in:
@@ -1,36 +1,203 @@
|
||||
import { Copy, Download, AlertTriangle } from "lucide-react";
|
||||
import { ReactNode } from "react";
|
||||
import { Copy, Download } from "lucide-react";
|
||||
import { InteractiveTextEditor } from "./InteractiveTextEditor";
|
||||
import { isValidEntityBoundary } from "@/app/utils/entityBoundary";
|
||||
import { EntityMapping } from "@/app/config/entityLabels"; // Importer l'interface unifiée
|
||||
|
||||
interface EntityMapping {
|
||||
originalValue: string;
|
||||
anonymizedValue: string;
|
||||
entityType: string;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
}
|
||||
// Supprimer l'interface locale et utiliser celle de entityLabels.ts
|
||||
|
||||
interface ResultPreviewComponentProps {
|
||||
outputText: string;
|
||||
sourceText: string;
|
||||
copyToClipboard: () => void;
|
||||
downloadText: () => void;
|
||||
highlightEntities: (text: string, mappings?: EntityMapping[]) => ReactNode;
|
||||
entityMappings?: EntityMapping[];
|
||||
onMappingsUpdate?: (mappings: EntityMapping[]) => void;
|
||||
}
|
||||
|
||||
export const ResultPreviewComponent = ({
|
||||
outputText,
|
||||
sourceText,
|
||||
copyToClipboard,
|
||||
downloadText,
|
||||
highlightEntities,
|
||||
entityMappings,
|
||||
entityMappings = [],
|
||||
onMappingsUpdate,
|
||||
}: ResultPreviewComponentProps) => {
|
||||
// SUPPRIMER cette ligne
|
||||
// const { mappings, updateMapping, removeMappingByValueWithOptions } = useEntityMappings(entityMappings);
|
||||
|
||||
// Utiliser directement entityMappings du parent
|
||||
const handleUpdateMapping = (
|
||||
originalValue: string,
|
||||
newLabel: string,
|
||||
entityType: string,
|
||||
applyToAllOccurrences: boolean = false,
|
||||
customColor?: string,
|
||||
wordStart?: number,
|
||||
wordEnd?: number
|
||||
) => {
|
||||
// Créer les nouveaux mappings directement
|
||||
const filteredMappings = entityMappings.filter(
|
||||
(mapping) => mapping.text !== originalValue
|
||||
);
|
||||
|
||||
const newMappings: EntityMapping[] = [];
|
||||
|
||||
if (applyToAllOccurrences) {
|
||||
// Appliquer à toutes les occurrences
|
||||
let searchIndex = 0;
|
||||
while (true) {
|
||||
const foundIndex = sourceText.indexOf(originalValue, searchIndex);
|
||||
if (foundIndex === -1) break;
|
||||
|
||||
if (isValidEntityBoundary(foundIndex, sourceText, originalValue)) {
|
||||
newMappings.push({
|
||||
text: originalValue,
|
||||
entity_type: entityType,
|
||||
start: foundIndex,
|
||||
end: foundIndex + originalValue.length,
|
||||
displayName: newLabel,
|
||||
customColor: customColor,
|
||||
});
|
||||
}
|
||||
searchIndex = foundIndex + 1;
|
||||
}
|
||||
} else {
|
||||
// CORRECTION: Utiliser wordStart/wordEnd pour cibler le mapping exact
|
||||
if (wordStart !== undefined && wordEnd !== undefined) {
|
||||
// Chercher le mapping exact avec les coordonnées précises
|
||||
const targetMapping = entityMappings.find(
|
||||
(mapping) => mapping.start === wordStart && mapping.end === wordEnd
|
||||
);
|
||||
|
||||
if (targetMapping) {
|
||||
// Mettre à jour le mapping existant spécifique
|
||||
const updatedMappings = entityMappings.map((m) => {
|
||||
if (m.start === wordStart && m.end === wordEnd) {
|
||||
return {
|
||||
...m,
|
||||
entity_type: entityType,
|
||||
displayName: newLabel,
|
||||
customColor: customColor,
|
||||
};
|
||||
}
|
||||
return m;
|
||||
});
|
||||
|
||||
onMappingsUpdate?.(updatedMappings);
|
||||
return;
|
||||
} else {
|
||||
// Créer un nouveau mapping aux coordonnées précises
|
||||
newMappings.push({
|
||||
text: originalValue,
|
||||
entity_type: entityType,
|
||||
start: wordStart,
|
||||
end: wordEnd,
|
||||
displayName: newLabel,
|
||||
customColor: customColor,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Fallback: logique existante si pas de coordonnées précises
|
||||
const existingMapping = entityMappings.find(
|
||||
(mapping) => mapping.text === originalValue
|
||||
);
|
||||
|
||||
if (existingMapping) {
|
||||
const updatedMappings = entityMappings.map((m) => {
|
||||
if (
|
||||
m.start === existingMapping.start &&
|
||||
m.end === existingMapping.end
|
||||
) {
|
||||
return {
|
||||
...m,
|
||||
entity_type: entityType,
|
||||
displayName: newLabel,
|
||||
customColor: customColor,
|
||||
};
|
||||
}
|
||||
return m;
|
||||
});
|
||||
|
||||
onMappingsUpdate?.(updatedMappings);
|
||||
return;
|
||||
} else {
|
||||
const foundIndex = sourceText.indexOf(originalValue);
|
||||
if (
|
||||
foundIndex !== -1 &&
|
||||
isValidEntityBoundary(foundIndex, sourceText, originalValue)
|
||||
) {
|
||||
newMappings.push({
|
||||
text: originalValue,
|
||||
entity_type: entityType,
|
||||
start: foundIndex,
|
||||
end: foundIndex + originalValue.length,
|
||||
displayName: newLabel,
|
||||
customColor: customColor,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notifier le parent avec les nouveaux mappings
|
||||
const allMappings = [...filteredMappings, ...newMappings];
|
||||
const uniqueMappings = allMappings.filter(
|
||||
(mapping, index, self) =>
|
||||
index ===
|
||||
self.findIndex(
|
||||
(m) => m.start === mapping.start && m.end === mapping.end
|
||||
)
|
||||
);
|
||||
|
||||
onMappingsUpdate?.(uniqueMappings.sort((a, b) => a.start - b.start));
|
||||
};
|
||||
|
||||
// NOUVELLE FONCTION: Gestion de la suppression avec applyToAll
|
||||
const handleRemoveMapping = (
|
||||
originalValue: string,
|
||||
applyToAll: boolean = false
|
||||
) => {
|
||||
console.log("handleRemoveMapping appelé:", {
|
||||
originalValue,
|
||||
applyToAll,
|
||||
});
|
||||
|
||||
// Notifier le parent avec les nouveaux mappings
|
||||
if (onMappingsUpdate) {
|
||||
const filteredMappings = entityMappings.filter(
|
||||
(mapping: EntityMapping) => {
|
||||
if (applyToAll) {
|
||||
// Supprimer toutes les occurrences
|
||||
return mapping.text !== originalValue;
|
||||
} else {
|
||||
// Supprimer seulement la première occurrence
|
||||
const firstOccurrenceIndex = entityMappings.findIndex(
|
||||
(m: EntityMapping) => m.text === originalValue
|
||||
);
|
||||
const currentIndex = entityMappings.indexOf(mapping);
|
||||
return !(
|
||||
mapping.text === originalValue &&
|
||||
currentIndex === firstOccurrenceIndex
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMappingsUpdate(
|
||||
filteredMappings.sort(
|
||||
(a: EntityMapping, b: EntityMapping) => a.start - b.start
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (!outputText) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-8 space-y-4">
|
||||
<div className="flex items-center justify-between border-b border-[#f7ab6e] border-opacity-30 pb-2">
|
||||
<h3 className="text-lg font-medium text-[#092727]">
|
||||
Document anonymisé
|
||||
Document anonymisé (Mode interactif)
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@@ -56,21 +223,12 @@ export const ResultPreviewComponent = ({
|
||||
|
||||
<div className="border border-[#f7ab6e] border-opacity-30 rounded-lg bg-white min-h-[400px] flex flex-col">
|
||||
<div className="flex-1 p-4 overflow-hidden">
|
||||
<div className="h-full min-h-[300px] text-[#092727] whitespace-pre-wrap overflow-y-auto">
|
||||
<div className="leading-relaxed">
|
||||
{highlightEntities(outputText, entityMappings)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-[#f7ab6e] border-opacity-30">
|
||||
<div className="flex items-start gap-2 p-2 bg-[#f7ab6e] bg-opacity-10 rounded-md">
|
||||
<AlertTriangle className="h-4 w-4 text-[#f7ab6e] mt-0.5 flex-shrink-0" />
|
||||
<p className="text-sm text-[#092727]">
|
||||
Vérifiez le résultat pour vous assurer que toutes les informations
|
||||
privées sont supprimées et éviter une divulgation accidentelle.
|
||||
</p>
|
||||
</div>
|
||||
<InteractiveTextEditor
|
||||
text={sourceText}
|
||||
entityMappings={entityMappings} // Utiliser entityMappings du parent au lieu de mappings
|
||||
onUpdateMapping={handleUpdateMapping}
|
||||
onRemoveMapping={handleRemoveMapping}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user