Deploy/batch 2 medium risk #2

Merged
madresebanoo merged 7 commits from deploy/batch-2-medium-risk into master 2026-09-02 11:50:34 +00:00
4 changed files with 54 additions and 42 deletions
Showing only changes of commit 0caea18eb8 - Show all commits
@@ -17,14 +17,13 @@
</div>
</div>
<div class="term-item__actions">
<div v-if="!term.isActive" class="term-item__status">
<span class="term-item__status-badge">
<span class="term-item__status-dot" />
غیرفعال
</span>
</div>
<div class="term-item__actions">
<ToggleSwitch
:model-value="!!term.isActive"
@update:model-value="emit('change-status', { id: term.id, isActive: !term.isActive })"
@@ -166,10 +165,6 @@ const createdDate = computed(() => 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;
@@ -25,12 +25,12 @@
<button
type="button"
class="users-table__role-badge"
:class="`users-table__role-badge--${roleName(user) || 'unknown'}`"
:class="`users-table__role-badge--${roleNames(user)[0] || 'unknown'}`"
:disabled="updateRoleMutation.isPending.value && activeUser?.id === user.id"
@click="onRoleClick($event, user)"
>
<span class="users-table__role-dot" />
<span>{{ roleLabel(user) || 'بدون نقش' }}</span>
<span>{{ roleLabelsText(user) || 'بدون نقش' }}</span>
<SvgIcon name="caret-down" :size="12" color="currentColor" />
</button>
<Badge v-if="user.status === 'blocked'" variant="danger" size="sm" dot>
@@ -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) => ({
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: `تغییر به ${r.label}`,
icon: 'arrows-clockwise',
onClick: () => onChangeRole(r.value),
}))
label: isAssigned ? `حذف نقش ${r.label}` : `افزودن نقش ${r.label}`,
icon: isAssigned ? 'close' : 'check',
danger: isAssigned,
disabled: isLastRole,
onClick: () => onToggleRole(r.value, isAssigned),
}
})
})
const menuItems = computed(() => {
+12 -10
View File
@@ -19,14 +19,15 @@
<div class="user-form__row">
<div class="user-form__cell user-form__cell--third">
<SelectField
v-model="form.role"
name="role"
label="نقش"
v-model="form.roles"
name="roles"
label="نقش‌ها"
:multiple="true"
:options="ROLES"
option-label="label"
option-value="value"
:error="errors.role"
@change="validateAt('role', form.role)"
:error="errors.roles"
@change="validateAt('roles', form.roles)"
/>
</div>
<div class="user-form__cell user-form__cell--third">
@@ -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,
+2 -2
View File
@@ -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,