Allow editing org team member name, mobile, and password from settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-04 12:31:07 +03:30
co-authored by Cursor
parent 39e2eb24a6
commit 75ff2c36c4
5 changed files with 219 additions and 1 deletions
+100 -1
View File
@@ -14,6 +14,7 @@ import {
type OrgUserDto,
type RolePermissionKey,
type UpdateOrganizationRequest,
type UpdateOrgUserRequest,
} from '@mento/shared';
import { canManageSettings, canManageUsers } from '@/lib/roles';
import { BUSINESS_FIELDS, BUSINESS_FIELD_LABELS } from '@/lib/business-fields';
@@ -71,6 +72,14 @@ const inviteMobile = ref('');
const invitePassword = ref('');
const inviteRoleId = ref('');
const editOpen = ref(false);
const editSaving = ref(false);
const editUserId = ref<string | null>(null);
const editName = ref('');
const editMobile = ref('');
const editPassword = ref('');
const editRoleLabel = ref('');
const roleFormOpen = ref(false);
const roleFormSaving = ref(false);
const roleFormId = ref<string | null>(null);
@@ -366,6 +375,47 @@ async function submitInvite() {
}
}
function openEditUser(user: OrgUserDto) {
menuUserId.value = null;
editUserId.value = user.id;
editName.value = user.fullName;
editMobile.value = user.mobile ?? '';
editPassword.value = '';
editRoleLabel.value = user.orgRoleName || '—';
editOpen.value = true;
error.value = '';
ok.value = '';
}
async function submitEditUser() {
if (!editUserId.value) return;
if (!editName.value.trim() || !editMobile.value.trim()) {
error.value = 'نام و شماره موبایل لازم است.';
return;
}
if (editPassword.value && editPassword.value.length < 8) {
error.value = 'رمز عبور در صورت ورود باید حداقل ۸ کاراکتر باشد.';
return;
}
editSaving.value = true;
error.value = '';
try {
const payload: UpdateOrgUserRequest = {
fullName: editName.value.trim(),
mobile: editMobile.value.trim(),
...(editPassword.value ? { password: editPassword.value } : {}),
};
await api.patch(`/users/${editUserId.value}`, payload);
editOpen.value = false;
ok.value = 'مشخصات کاربر ذخیره شد.';
await load();
} catch (e) {
error.value = apiError(e, 'ویرایش کاربر ناموفق بود');
} finally {
editSaving.value = false;
}
}
async function toggleStatus(user: OrgUserDto) {
menuUserId.value = null;
error.value = '';
@@ -852,6 +902,7 @@ onUnmounted(() => {
<thead>
<tr>
<th>نام و نام خانوادگی</th>
<th>موبایل</th>
<th>نقش</th>
<th>پورسانت %</th>
<th>وضعیت</th>
@@ -872,6 +923,7 @@ onUnmounted(() => {
<strong>{{ user.fullName }}</strong>
</div>
</td>
<td class="muted" dir="ltr">{{ user.mobile || '—' }}</td>
<td>
<span class="pill" :data-color="user.orgRoleColor || 'teal'">
{{ user.orgRoleName || '—' }}
@@ -910,6 +962,13 @@ onUnmounted(() => {
<UiIcon name="dots-v" :size="16" />
</button>
</template>
<button
v-if="canInvite"
type="button"
@click="openEditUser(user)"
>
ویرایش مشخصات
</button>
<button type="button" @click="toggleStatus(user)">
{{ user.isActive === false ? 'فعال کردن' : 'غیرفعال کردن' }}
</button>
@@ -917,7 +976,7 @@ onUnmounted(() => {
</td>
</tr>
<tr v-if="!loading && !pagedUsers.length">
<td colspan="6" class="empty">کاربری یافت نشد.</td>
<td colspan="7" class="empty">کاربری یافت نشد.</td>
</tr>
</tbody>
</table>
@@ -932,6 +991,7 @@ onUnmounted(() => {
<span class="pill" :data-color="user.orgRoleColor || 'teal'">
{{ user.orgRoleName || '—' }}
</span>
<p v-if="user.mobile" class="muted mobile-line" dir="ltr">{{ user.mobile }}</p>
</div>
</div>
<div class="card-meta" @click.stop>
@@ -953,6 +1013,14 @@ onUnmounted(() => {
<i />
{{ user.isActive === false ? 'غیرفعال' : 'فعال' }}
</span>
<button
v-if="canInvite"
type="button"
class="btn ghost"
@click="openEditUser(user)"
>
ویرایش
</button>
</div>
</article>
<p v-if="!loading && !pagedUsers.length" class="empty">کاربری یافت نشد.</p>
@@ -1027,6 +1095,32 @@ onUnmounted(() => {
</template>
</UiModal>
<UiModal :open="editOpen" title="ویرایش مشخصات کاربر" @close="editOpen = false">
<form class="form" @submit.prevent="submitEditUser">
<label>نام و نام خانوادگی<input v-model="editName" required minlength="2" /></label>
<label>
شماره موبایل
<input v-model="editMobile" dir="ltr" inputmode="tel" placeholder="0912…" required />
</label>
<label>
رمز عبور جدید
<input
v-model="editPassword"
type="password"
minlength="8"
placeholder="خالی بگذارید اگر تغییر نمی‌دهید"
/>
</label>
<p class="hint">نقش فعلی: {{ editRoleLabel }} — برای تغییر نقش از بخش نقش‌ها استفاده کنید.</p>
</form>
<template #footer>
<button type="button" class="btn ghost" @click="editOpen = false">انصراف</button>
<button type="button" class="btn primary" :disabled="editSaving" @click="submitEditUser">
{{ editSaving ? 'در حال ذخیره…' : 'ذخیره' }}
</button>
</template>
</UiModal>
<UiModal
:open="roleFormOpen"
:title="roleFormId ? 'ویرایش نقش' : roleFormParentId ? 'زیرنقش جدید' : 'نقش جدید'"
@@ -1627,6 +1721,11 @@ tbody tr + tr td {
margin-top: 0.35rem;
}
.mobile-line {
margin: 0.2rem 0 0;
font-size: 0.78rem;
}
.card-meta {
display: grid;
justify-items: end;