diff --git a/src/features/admin/terms/components/TermItem.vue b/src/features/admin/terms/components/TermItem.vue index 7e1715f..f717c34 100644 --- a/src/features/admin/terms/components/TermItem.vue +++ b/src/features/admin/terms/components/TermItem.vue @@ -17,14 +17,13 @@ -
- - - غیرفعال - -
-
+
+ + + غیرفعال + +
formatJalaaliDate(props.term.createdAt) || '' font-size: 0.75rem; } - &__status { - flex: 0 0 auto; - } - &__status-badge { display: inline-flex; align-items: center; @@ -191,6 +186,7 @@ const createdDate = computed(() => formatJalaaliDate(props.term.createdAt) || '' &__actions { display: flex; + flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 0.375rem; diff --git a/src/features/admin/users/components/UsersTable.vue b/src/features/admin/users/components/UsersTable.vue index a525b4b..3b9089d 100644 --- a/src/features/admin/users/components/UsersTable.vue +++ b/src/features/admin/users/components/UsersTable.vue @@ -25,12 +25,12 @@ @@ -96,15 +96,13 @@ const queryClient = useQueryClient() const emit = defineEmits(['edit', 'delete', 'block', 'unblock', 'show-details']) -const roleName = (user) => { - const first = user.roles?.[0] - return typeof first === 'string' ? first : first?.name -} +const roleNames = (user) => + (user.roles || []).map((r) => (typeof r === 'string' ? r : r?.name)).filter(Boolean) -const roleLabel = (user) => { - const name = roleName(user) - if (!name) return '' - return ROLES.find((r) => r.value === name)?.label || name +const roleLabelsText = (user) => { + const names = roleNames(user) + if (names.length === 0) return '' + return names.map((name) => ROLES.find((r) => r.value === name)?.label || name).join('، ') } const menuOpen = ref(false) @@ -140,23 +138,39 @@ const onRoleClick = (event, user) => { roleMenuOpen.value = true } -const onChangeRole = (targetRoleName) => { +// Toggles one role's membership without touching the user's other roles — +// access must stay additive, so this can never collapse into a single-role +// replace the way the old "switch to X" action used to. +const onToggleRole = (targetRoleName, isAssigned) => { const user = activeUser.value if (!user || !targetRoleName) return + const current = roleNames(user) + const nextRoles = isAssigned + ? current.filter((r) => r !== targetRoleName) + : [...current, targetRoleName] + if (nextRoles.length === 0) return updateRoleMutation.mutate( - { id: user.id, payload: { roles: [targetRoleName] } }, + { id: user.id, payload: { roles: nextRoles } }, { onSuccess: invalidateUsers } ) } const roleMenuItems = computed(() => { - const current = activeUser.value ? roleName(activeUser.value) : null - return ROLES.filter((r) => r.changeable && r.value !== current).map((r) => ({ - key: `role-${r.value}`, - label: `تغییر به ${r.label}`, - icon: 'arrows-clockwise', - onClick: () => onChangeRole(r.value), - })) + const user = activeUser.value + if (!user) return [] + const current = roleNames(user) + return ROLES.filter((r) => r.changeable).map((r) => { + const isAssigned = current.includes(r.value) + const isLastRole = isAssigned && current.length <= 1 + return { + key: `role-${r.value}`, + label: isAssigned ? `حذف نقش ${r.label}` : `افزودن نقش ${r.label}`, + icon: isAssigned ? 'close' : 'check', + danger: isAssigned, + disabled: isLastRole, + onClick: () => onToggleRole(r.value, isAssigned), + } + }) }) const menuItems = computed(() => { diff --git a/src/features/admin/users/pages/UserFormPage.vue b/src/features/admin/users/pages/UserFormPage.vue index 46b1f96..70eada8 100644 --- a/src/features/admin/users/pages/UserFormPage.vue +++ b/src/features/admin/users/pages/UserFormPage.vue @@ -19,14 +19,15 @@
@@ -265,7 +266,7 @@ const { data: cities = ref([]) } = useGetCitiesOfProvinceQuery(provinceIdRef, { const initialNationalCode = ref('') const form = ref({ - role: '', + roles: [], name: '', birthday: '', nationalCode: '', @@ -307,11 +308,12 @@ watch( existingUser, (user) => { if (!user) return - const firstRole = Array.isArray(user.roles) ? user.roles[0] : null - const roleValue = typeof firstRole === 'object' ? firstRole?.name : firstRole + const roleValues = Array.isArray(user.roles) + ? user.roles.map((r) => (typeof r === 'object' ? r?.name : r)).filter(Boolean) + : [] form.value = { ...form.value, - role: roleValue || '', + roles: roleValues, name: user.name || `${user.firstName || ''} ${user.lastName || ''}`.trim(), birthday: user.birthday || '', nationalCode: user.nationalCode || '', @@ -360,7 +362,7 @@ const submitting = computed(() => addMutation.isPending.value || updateMutation. const buildUpdatePayload = () => { return { name: form.value.name, - roles: form.value.role ? [form.value.role] : [], + roles: form.value.roles, provinceId: form.value.provinceId || null, cityId: form.value.cityId || null, marriageStatus: form.value.maritalStatus || null, diff --git a/src/features/admin/users/schema/index.js b/src/features/admin/users/schema/index.js index 59c0834..6ec5475 100644 --- a/src/features/admin/users/schema/index.js +++ b/src/features/admin/users/schema/index.js @@ -1,4 +1,4 @@ -import { number, object, string } from 'yup' +import { array, number, object, string } from 'yup' import { passwordRule } from '@/constants/rules/passwordRule' import { phoneNumberRule } from '@/constants/rules/phoneNumberRule' import { nationalCodeRule } from '@/constants/rules/nationalCodeRule' @@ -9,7 +9,7 @@ export const rejectionReasonSchema = object().shape({ }) const baseUserShape = { - role: string().required(), + roles: array().of(string().required()).min(1, 'حداقل یک نقش را انتخاب کنید').required(), name: string().required().min(3), phone: phoneNumberRule, nationalCode: nationalCodeRule,