Skip to content

Commit 33444fb

Browse files
committed
fix: solve issue with DDI
1 parent 01fd565 commit 33444fb

File tree

7 files changed

+236
-49
lines changed

7 files changed

+236
-49
lines changed

src/packages/components/rich-editor/react-md-editor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const MDEditor = ({
1818
onChange={handleChange}
1919
commands={[italic, bold, unorderedListCommand, link]}
2020
preview="edit"
21+
data-color-mode="light"
2122
/>
2223
);
2324
};

src/packages/modules-ddi/physical-instances/components/CodeRepresentation/CodeListDataTable.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ vi.mock("primereact/button", () => ({
4242
),
4343
}));
4444

45-
vi.mock("primereact/menu", () => ({
46-
Menu: vi.fn().mockImplementation(() => null),
45+
vi.mock("primereact/overlaypanel", () => ({
46+
OverlayPanel: vi.fn().mockImplementation(() => null),
4747
}));
4848

4949
vi.mock("primereact/datatable", () => ({

src/packages/modules-ddi/physical-instances/components/CodeRepresentation/CodeListDataTable.tsx

Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import { InputText } from "primereact/inputtext";
33
import { Button } from "primereact/button";
44
import { DataTable } from "primereact/datatable";
55
import { Column } from "primereact/column";
6-
import { Menu } from "primereact/menu";
6+
import { OverlayPanel } from "primereact/overlaypanel";
77
import { useTranslation } from "react-i18next";
8-
import type { MenuItem } from "primereact/menuitem";
98

109
export interface CodeTableRow {
1110
id: string;
@@ -35,7 +34,7 @@ export const CodeListDataTable = ({
3534
onMoveCode,
3635
}: Readonly<CodeListDataTableProps>) => {
3736
const { t } = useTranslation();
38-
const menuRefs = useRef<Map<string, Menu | null>>(new Map());
37+
const overlayRefs = useRef<Map<string, OverlayPanel | null>>(new Map());
3938
const inputRefs = useRef<Map<string, HTMLInputElement | null>>(new Map());
4039
const [shouldFocusNewCode, setShouldFocusNewCode] = useState(false);
4140
const previousCodesLength = useRef(codes.length);
@@ -90,53 +89,70 @@ export const CodeListDataTable = ({
9089
);
9190
};
9291

93-
const getMenuItems = (rowData: CodeTableRow, index: number): MenuItem[] => {
94-
const items: MenuItem[] = [];
95-
96-
if (onMoveCode && index > 0) {
97-
items.push({
98-
label: t("physicalInstance.view.code.moveUp"),
99-
icon: "pi pi-arrow-up",
100-
command: () => onMoveCode(rowData.id, "up"),
101-
});
102-
}
103-
104-
if (onMoveCode && index < codes.length - 1) {
105-
items.push({
106-
label: t("physicalInstance.view.code.moveDown"),
107-
icon: "pi pi-arrow-down",
108-
command: () => onMoveCode(rowData.id, "down"),
109-
});
110-
}
111-
112-
items.push({
113-
label: t("physicalInstance.view.code.deleteCode"),
114-
icon: "pi pi-trash",
115-
className: "p-menuitem-danger",
116-
command: () => onDeleteCode(rowData.id),
117-
});
118-
119-
return items;
120-
};
121-
122-
const actionBodyTemplate = (rowData: CodeTableRow, options: { rowIndex: number }) => {
123-
const menuRef = (el: Menu | null) => {
124-
menuRefs.current.set(rowData.id, el);
92+
const actionBodyTemplate = (rowData: CodeTableRow) => {
93+
const overlayRef = (el: OverlayPanel | null) => {
94+
overlayRefs.current.set(rowData.id, el);
12595
};
12696

12797
const handleMenuToggle = (e: React.MouseEvent) => {
98+
e.stopPropagation();
99+
128100
// Fermer tous les autres menus avant d'ouvrir celui-ci
129-
menuRefs.current.forEach((menu, id) => {
130-
if (id !== rowData.id && menu) {
131-
menu.hide(e);
101+
overlayRefs.current.forEach((overlay, id) => {
102+
if (id !== rowData.id && overlay) {
103+
overlay.hide();
132104
}
133105
});
134-
menuRefs.current.get(rowData.id)?.toggle(e);
106+
107+
overlayRefs.current.get(rowData.id)?.toggle(e);
135108
};
136109

110+
// Always recalculate index from current codes array
111+
const currentIndex = codes.findIndex((c) => c.id === rowData.id);
112+
const canMoveUp = onMoveCode && currentIndex > 0;
113+
const canMoveDown = onMoveCode && currentIndex < codes.length - 1;
114+
137115
return (
138116
<div className="flex gap-2">
139-
<Menu model={getMenuItems(rowData, options.rowIndex)} popup ref={menuRef} />
117+
<OverlayPanel ref={overlayRef}>
118+
<div className="flex flex-column gap-2" style={{ minWidth: "200px" }}>
119+
{canMoveUp && (
120+
<Button
121+
type="button"
122+
label={t("physicalInstance.view.code.moveUp")}
123+
icon="pi pi-arrow-up"
124+
text
125+
onClick={() => {
126+
onMoveCode(rowData.id, "up");
127+
overlayRefs.current.get(rowData.id)?.hide();
128+
}}
129+
/>
130+
)}
131+
{canMoveDown && (
132+
<Button
133+
type="button"
134+
label={t("physicalInstance.view.code.moveDown")}
135+
icon="pi pi-arrow-down"
136+
text
137+
onClick={() => {
138+
onMoveCode(rowData.id, "down");
139+
overlayRefs.current.get(rowData.id)?.hide();
140+
}}
141+
/>
142+
)}
143+
<Button
144+
type="button"
145+
label={t("physicalInstance.view.code.deleteCode")}
146+
icon="pi pi-trash"
147+
text
148+
severity="danger"
149+
onClick={() => {
150+
onDeleteCode(rowData.id);
151+
overlayRefs.current.get(rowData.id)?.hide();
152+
}}
153+
/>
154+
</div>
155+
</OverlayPanel>
140156
<Button
141157
type="button"
142158
icon="pi pi-ellipsis-v"
@@ -162,7 +178,13 @@ export const CodeListDataTable = ({
162178
onChange={(e) => onCodeListLabelChange(e.target.value)}
163179
/>
164180
</div>
165-
<DataTable value={codes} size="small" emptyMessage={t("physicalInstance.view.code.noCodes")}>
181+
<DataTable
182+
value={codes}
183+
size="small"
184+
emptyMessage={t("physicalInstance.view.code.noCodes")}
185+
dataKey="id"
186+
key={codes.map((c) => c.id).join("-")}
187+
>
166188
<Column
167189
field="value"
168190
header={t("physicalInstance.view.code.value")}

src/packages/modules-ddi/physical-instances/components/CodeRepresentation/CodeRepresentation.reducer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface CodeRepresentationState {
1010

1111
export type CodeRepresentationAction =
1212
| { type: "SET_CODE_LIST_LABEL"; payload: string }
13+
| { type: "SYNC_CODE_LIST_LABEL"; payload: string }
1314
| { type: "SET_CODES"; payload: CodeTableRow[] }
1415
| { type: "ADD_CODE"; payload: CodeTableRow }
1516
| {
@@ -45,6 +46,13 @@ export const codeRepresentationReducer = (
4546
case "SET_CODE_LIST_LABEL":
4647
return { ...state, codeListLabel: action.payload };
4748

49+
case "SYNC_CODE_LIST_LABEL":
50+
// Only sync if current label is empty (to avoid overwriting user edits)
51+
if (!state.codeListLabel) {
52+
return { ...state, codeListLabel: action.payload };
53+
}
54+
return state;
55+
4856
case "SET_CODES":
4957
return { ...state, codes: action.payload };
5058

src/packages/modules-ddi/physical-instances/components/CodeRepresentation/CodeRepresentation.spec.tsx

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ vi.mock("primereact/datatable", () => ({
9595
},
9696
}));
9797

98-
vi.mock("primereact/menu", () => ({
99-
Menu: vi.fn().mockImplementation(() => null),
98+
vi.mock("primereact/overlaypanel", () => ({
99+
OverlayPanel: vi.fn().mockImplementation(() => null),
100100
}));
101101

102102
vi.mock("primereact/column", () => ({
@@ -387,5 +387,129 @@ describe("CodeRepresentation", () => {
387387
const labelInput = screen.getByLabelText("Libellé de la liste de codes") as HTMLInputElement;
388388
expect(labelInput.value).toBe("Liste modifiée");
389389
});
390+
391+
it("should preserve label when codeList content changes but ID stays the same", () => {
392+
const { rerender } = render(
393+
<CodeRepresentation
394+
representation={mockRepresentation}
395+
codeList={mockCodeList}
396+
categories={mockCategories}
397+
onChange={mockOnChange}
398+
/>,
399+
);
400+
401+
// Modifier le label localement
402+
const labelInput = screen.getByLabelText("Libellé de la liste de codes");
403+
fireEvent.change(labelInput, {
404+
target: { value: "Label modifié par l'utilisateur" },
405+
});
406+
407+
// Simuler une mise à jour du codeList avec le même ID (comme lors de l'ajout d'un code)
408+
const updatedCodeList: CodeList = {
409+
...mockCodeList,
410+
Code: [
411+
...mockCodeList.Code,
412+
{
413+
"@isUniversallyUnique": "true",
414+
URN: "urn:ddi:fr.insee:code-2:1",
415+
Agency: "fr.insee",
416+
ID: "code-2",
417+
Version: "1",
418+
CategoryReference: {
419+
Agency: "fr.insee",
420+
ID: "category-2",
421+
Version: "1",
422+
TypeOfObject: "Category",
423+
},
424+
Value: "2",
425+
},
426+
],
427+
};
428+
429+
rerender(
430+
<CodeRepresentation
431+
representation={mockRepresentation}
432+
codeList={updatedCodeList}
433+
categories={mockCategories}
434+
onChange={mockOnChange}
435+
/>,
436+
);
437+
438+
// Le label devrait être préservé car l'ID n'a pas changé
439+
const labelInputAfter = screen.getByLabelText(
440+
"Libellé de la liste de codes",
441+
) as HTMLInputElement;
442+
expect(labelInputAfter.value).toBe("Label modifié par l'utilisateur");
443+
});
444+
});
445+
446+
describe("label preservation during editing", () => {
447+
it("should preserve label when adding a code", () => {
448+
render(
449+
<CodeRepresentation
450+
representation={mockRepresentation}
451+
codeList={mockCodeList}
452+
categories={mockCategories}
453+
onChange={mockOnChange}
454+
/>,
455+
);
456+
457+
// Modifier le label
458+
const labelInput = screen.getByLabelText("Libellé de la liste de codes");
459+
fireEvent.change(labelInput, { target: { value: "Mon label" } });
460+
461+
// Ajouter un code
462+
const addButton = screen.getByText("Ajouter un code");
463+
fireEvent.click(addButton);
464+
465+
// Vérifier que onChange a été appelé avec le label préservé
466+
expect(mockOnChange).toHaveBeenCalledWith(
467+
expect.anything(),
468+
expect.objectContaining({
469+
Label: {
470+
Content: {
471+
"@xml:lang": "fr-FR",
472+
"#text": "Mon label",
473+
},
474+
},
475+
}),
476+
expect.anything(),
477+
);
478+
});
479+
480+
it("should preserve label when editing a code value", () => {
481+
render(
482+
<CodeRepresentation
483+
representation={mockRepresentation}
484+
codeList={mockCodeList}
485+
categories={mockCategories}
486+
onChange={mockOnChange}
487+
/>,
488+
);
489+
490+
// Modifier le label
491+
const labelInput = screen.getByLabelText("Libellé de la liste de codes");
492+
fireEvent.change(labelInput, { target: { value: "Mon label" } });
493+
494+
vi.clearAllMocks();
495+
496+
// Modifier un code (via l'input dans le tableau)
497+
const codeInputs = screen.getAllByPlaceholderText("Valeur");
498+
fireEvent.change(codeInputs[0], { target: { value: "10" } });
499+
500+
// Vérifier que onChange a été appelé avec le label préservé
501+
expect(mockOnChange).toHaveBeenCalledWith(
502+
expect.anything(),
503+
expect.objectContaining({
504+
Label: {
505+
Content: {
506+
"@xml:lang": "fr-FR",
507+
"#text": "Mon label",
508+
},
509+
},
510+
}),
511+
expect.anything(),
512+
);
513+
});
390514
});
391515
});

0 commit comments

Comments
 (0)