From c2375a53069903b918577620a21f2fd98b4001ee Mon Sep 17 00:00:00 2001 From: Nathnael Date: Tue, 28 Jul 2026 12:27:14 +0000 Subject: [PATCH] fix(backoffice): scope copy-permissions-from to the selected unit (EDRFREIGHT-308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Copy Permissions From" dropdown in the position-type form only filtered candidates by the selected organization's full unit set, ignoring the form's own "Select Unit" field entirely — so switching units never re-scoped the list, unlike the equivalent unit filter on the position-management table. Co-Authored-By: Claude Sonnet 5 --- .../src/locales/am/translation.json | 1 + .../src/locales/en/translation.json | 1 + .../src/locales/fr/translation.json | 1 + .../CreatePositionForm.tsx | 46 +++++++++---------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/locales/am/translation.json b/apps/edr-freight-web/backoffice/src/locales/am/translation.json index 99c1e0329..1353a9b33 100644 --- a/apps/edr-freight-web/backoffice/src/locales/am/translation.json +++ b/apps/edr-freight-web/backoffice/src/locales/am/translation.json @@ -2653,6 +2653,7 @@ "copyPermissionsHint": "የነበረ የቦታ አይነት ይምረጡ፤ ፍቃዶቹ አስቀድመው ይሞላሉ፣ ከታች ማስተካከል ይችላሉ።", "copyPermissionsFailed": "ፍቃዶችን መቅዳት አልተቻለም", "selectOrganizationToCopy": "መቅዳት የሚችሏቸውን የቦታ ዓይነቶች ለማየት መጀመሪያ ድርጅት ይምረጡ", + "selectUnitToCopy": "መቅዳት የሚችሏቸውን የቦታ ዓይነቶች ለማየት መጀመሪያ ክፍል ይምረጡ", "cannotClearAllPermissions": "ተቀምጧል። ፍቃዶቹ አልተቀየሩም — ይህ የቦታ ዓይነት ቢያንስ አንድ ፍቃድ ሊኖረው ይገባል።", "permissionsSelected": "{{count}} ተመርጠዋል", "positionTypeCreated": "የቦታ ዓይነት ተፈጥሯል", diff --git a/apps/edr-freight-web/backoffice/src/locales/en/translation.json b/apps/edr-freight-web/backoffice/src/locales/en/translation.json index f1357c2bf..1a2a95244 100644 --- a/apps/edr-freight-web/backoffice/src/locales/en/translation.json +++ b/apps/edr-freight-web/backoffice/src/locales/en/translation.json @@ -2762,6 +2762,7 @@ "copyPermissionsHint": "Pick an existing position type to pre-fill its permissions, then edit below.", "copyPermissionsFailed": "Failed to copy permissions", "selectOrganizationToCopy": "Select an organization to see the position types you can copy from", + "selectUnitToCopy": "Select a unit to see the position types you can copy from", "cannotClearAllPermissions": "Saved. Permissions were left unchanged — this position type must keep at least one permission.", "permissionsSelected": "{{count}} selected", "positionTypeCreated": "Position type created", diff --git a/apps/edr-freight-web/backoffice/src/locales/fr/translation.json b/apps/edr-freight-web/backoffice/src/locales/fr/translation.json index bd5e17fae..5019a2fb8 100644 --- a/apps/edr-freight-web/backoffice/src/locales/fr/translation.json +++ b/apps/edr-freight-web/backoffice/src/locales/fr/translation.json @@ -1888,6 +1888,7 @@ "copyPermissionsHint": "Choisissez un type de poste existant pour préremplir ses autorisations, puis modifiez ci-dessous.", "copyPermissionsFailed": "Échec de la copie des autorisations", "selectOrganizationToCopy": "Sélectionnez une organisation pour voir les types de poste que vous pouvez copier", + "selectUnitToCopy": "Sélectionnez une unité pour voir les types de poste que vous pouvez copier", "cannotClearAllPermissions": "Enregistré. Les autorisations n'ont pas été modifiées — ce type de poste doit conserver au moins une autorisation.", "permissionsSelected": "{{count}} sélectionné(s)", "positionTypeCreated": "Type de poste créé", diff --git a/apps/edr-freight-web/backoffice/src/user-management/components/position-management/CreatePositionForm.tsx b/apps/edr-freight-web/backoffice/src/user-management/components/position-management/CreatePositionForm.tsx index f8fc0cfdd..29ac1486f 100644 --- a/apps/edr-freight-web/backoffice/src/user-management/components/position-management/CreatePositionForm.tsx +++ b/apps/edr-freight-web/backoffice/src/user-management/components/position-management/CreatePositionForm.tsx @@ -115,6 +115,7 @@ export const CreatePositionForm = ({ }); const selectedOrganizationId = form.watch("organizationId"); + const selectedUnitId = form.watch("unitId"); const { organizationsResponse, isLoading: isLoadingOrgs } = useOrganizations( "Org", @@ -163,36 +164,32 @@ export const CreatePositionForm = ({ enabled: mode === "edit" && !!positionTypeId, }); - // A position type belongs to a unit, and a unit to an organization — IAM has - // no organizationId on the type itself and no organization-scoped route, so - // the picked org narrows the list through its units. isSystem types are the - // shared "commons" and stay available to every organization. - const orgUnitIds = useMemo( - () => - new Set( - (unitsResponse?.data?.items ?? []).map((unit: UnitDto) => unit.id), - ), - [unitsResponse], - ); - + // A position type belongs to a single unit — scope copy sources to the + // selected unit, same as the "Select Unit" filter on the position list page. + // isSystem types are the shared "commons" and stay available everywhere. const copyFromOptions = useMemo(() => { - if (!selectedOrganizationId) return []; + if (!selectedUnitId) return []; return positionTypes.filter( (type: PositionTypeDto) => type.id !== positionTypeId && - (type.isSystem || (!!type.unitId && orgUnitIds.has(type.unitId))), + (type.isSystem || type.unitId === selectedUnitId), ); - }, [positionTypes, orgUnitIds, selectedOrganizationId, positionTypeId]); + }, [positionTypes, selectedUnitId, positionTypeId]); // Reset the selected unit when the organization changes so a unit from a - // different org can't be submitted by mistake. The copy source is cleared - // too — it is scoped to the old organization. + // different org can't be submitted by mistake. useEffect(() => { if (mode === "edit") return; form.setValue("unitId", ""); - setCopyFromPositionId(""); }, [selectedOrganizationId, mode, form]); + // The copy source is scoped to the selected unit — clear it whenever the + // unit changes (including as a side effect of the org reset above) so a + // stale selection from a different unit can't be submitted. + useEffect(() => { + setCopyFromPositionId(""); + }, [selectedUnitId]); + useEffect(() => { if (mode !== "edit" || !initialValues || !positionTypeId) return; if (hasLoadedEditData.current) return; @@ -335,11 +332,13 @@ export const CreatePositionForm = ({ const copyFromPlaceholder = !selectedOrganizationId ? t("contentManagement.selectOrganizationToCopy") - : isCopying || isLoadingPositionTypes || isLoadingUnits - ? t("common.loading") - : isErrorPositionTypes - ? t("contentManagement.failedToLoadPositionTypes") - : t("contentManagement.selectPositionToCopy"); + : !selectedUnitId + ? t("contentManagement.selectUnitToCopy") + : isCopying || isLoadingPositionTypes || isLoadingUnits + ? t("common.loading") + : isErrorPositionTypes + ? t("contentManagement.failedToLoadPositionTypes") + : t("contentManagement.selectPositionToCopy"); return (
@@ -454,6 +453,7 @@ export const CreatePositionForm = ({ onValueChange={handleCopyFrom} disabled={ !selectedOrganizationId || + !selectedUnitId || isLoadingPositionTypes || isLoadingUnits || isCopying