Files
fabric_trade_admin/src/views/users/UsersView.vue
T

285 lines
9.2 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import BaseModal from '@/components/ui/BaseModal.vue'
import BaseSwitch from '@/components/ui/BaseSwitch.vue'
import BaseEmptyState from '@/components/ui/BaseEmptyState.vue'
import ActiveSwitchCell from '@/components/ui/ActiveSwitchCell.vue'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import { adminUsers as initialUsers } from '@/data/mockData'
import { useToast } from '@/composables/useToast'
import { USER_ROLE_LABELS } from '@/utils/adminLabels'
import { formatDateTime } from '@/utils/format'
import type { AdminUser, UserRole } from '@/types'
const userList = ref<AdminUser[]>([...initialUsers])
const searchQuery = ref('')
const modalMode = ref<'add' | 'edit' | null>(null)
const editingId = ref<string | null>(null)
const formError = ref('')
const saving = ref(false)
const toast = useToast()
const emptyForm = () => ({
fullName: '',
phone: '',
email: '',
role: 'sales' as UserRole,
isActive: true,
})
const form = reactive(emptyForm())
const isModalOpen = computed(() => modalMode.value !== null)
const modalTitle = computed(() => (modalMode.value === 'edit' ? 'ویرایش کاربر' : 'افزودن کاربر'))
const isFormValid = computed(
() => form.fullName.trim() && form.phone.trim() && form.email.trim(),
)
const filteredUsers = computed(() => {
const q = searchQuery.value.trim()
if (!q) return userList.value
return userList.value.filter(
(u) =>
u.fullName.includes(q) ||
u.phone.includes(q) ||
u.email.includes(q) ||
USER_ROLE_LABELS[u.role].includes(q),
)
})
function openAddModal() {
modalMode.value = 'add'
editingId.value = null
Object.assign(form, emptyForm())
formError.value = ''
}
function openEditModal(user: AdminUser) {
modalMode.value = 'edit'
editingId.value = user.id
form.fullName = user.fullName
form.phone = user.phone
form.email = user.email
form.role = user.role
form.isActive = user.isActive
formError.value = ''
}
function closeModal() {
modalMode.value = null
editingId.value = null
formError.value = ''
}
function submitUser() {
if (!isFormValid.value) {
formError.value = 'نام، تلفن و ایمیل الزامی هستند.'
return
}
saving.value = true
const payload = {
fullName: form.fullName.trim(),
phone: form.phone.trim(),
email: form.email.trim(),
role: form.role,
isActive: form.isActive,
}
if (modalMode.value === 'edit' && editingId.value) {
const index = userList.value.findIndex((u) => u.id === editingId.value)
if (index !== -1) {
userList.value[index] = { ...userList.value[index]!, ...payload }
}
} else {
userList.value.push({
id: `usr-${crypto.randomUUID().slice(0, 8)}`,
...payload,
lastLoginAt: null,
})
}
toast.success(modalMode.value === 'edit' ? 'اطلاعات کاربر به‌روزرسانی شد.' : 'کاربر جدید ثبت شد.')
saving.value = false
closeModal()
}
function clearSearch() {
searchQuery.value = ''
}
</script>
<template>
<div class="users-page">
<div class="admin-page-header">
<div>
<h1 class="admin-page-header__title">کاربران و دسترسی‌ها</h1>
<p class="admin-page-header__breadcrumb">سیستم / کاربران</p>
</div>
<button type="button" class="admin-btn admin-btn--primary" @click="openAddModal">
افزودن کاربر
</button>
</div>
<div class="users-page__search admin-card">
<AdminIcon name="search" :size="18" />
<input
v-model="searchQuery"
type="search"
class="users-page__search-input"
placeholder="جستجو در نام، تلفن، ایمیل..."
/>
</div>
<BaseEmptyState
v-if="!filteredUsers.length"
class="admin-card"
icon="users"
:title="searchQuery.trim() ? 'نتیجه‌ای یافت نشد' : 'کاربری ثبت نشده'"
:description="
searchQuery.trim()
? 'عبارت جستجو را تغییر دهید یا فیلترها را پاک کنید.'
: 'برای شروع، اولین کاربر را ثبت کنید.'
"
>
<button v-if="searchQuery.trim()" type="button" class="admin-btn admin-btn--outline admin-btn--sm" @click="clearSearch">
پاک کردن جستجو
</button>
<button v-else type="button" class="admin-btn admin-btn--primary admin-btn--sm" @click="openAddModal">
افزودن کاربر
</button>
</BaseEmptyState>
<div v-else class="users-page__table admin-card desktop-only">
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>نام</th>
<th>تلفن</th>
<th>ایمیل</th>
<th>نقش</th>
<th class="status">وضعیت</th>
<th>آخرین ورود</th>
<th class="actions">عملیات</th>
</tr>
</thead>
<tbody>
<tr v-for="user in filteredUsers" :key="user.id">
<td>{{ user.fullName }}</td>
<td>{{ user.phone }}</td>
<td dir="ltr">{{ user.email }}</td>
<td>{{ USER_ROLE_LABELS[user.role] }}</td>
<td class="status">
<ActiveSwitchCell
:id="user.id"
v-model="user.isActive"
:entity-name="user.fullName"
entity-type="کاربر"
/>
</td>
<td>{{ user.lastLoginAt ? formatDateTime(user.lastLoginAt) : '—' }}</td>
<td class="actions">
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="openEditModal(user)">
ویرایش
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-if="filteredUsers.length" class="compact-only admin-card-list">
<article v-for="user in filteredUsers" :key="user.id" class="admin-card-item">
<div class="admin-card-item__header">
<p class="admin-card-item__title">{{ user.fullName }}</p>
<ActiveSwitchCell
:id="user.id"
v-model="user.isActive"
:entity-name="user.fullName"
entity-type="کاربر"
/>
</div>
<div class="admin-card-item__meta">
<span>{{ USER_ROLE_LABELS[user.role] }}</span>
<span>{{ user.phone }}</span>
<span dir="ltr">{{ user.email }}</span>
</div>
<div class="admin-card-item__actions">
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="openEditModal(user)">
ویرایش
</button>
</div>
</article>
</div>
<BaseModal :open="isModalOpen" :title="modalTitle" size="lg" @close="closeModal">
<form class="admin-form" @submit.prevent="submitUser">
<p v-if="formError" class="admin-form__error" role="alert">{{ formError }}</p>
<div class="admin-form__field">
<label for="user-name">نام کامل <span class="admin-form__required">*</span></label>
<input id="user-name" v-model="form.fullName" type="text" class="admin-input" />
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="user-phone">تلفن <span class="admin-form__required">*</span></label>
<input id="user-phone" v-model="form.phone" type="text" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="user-email">ایمیل <span class="admin-form__required">*</span></label>
<input id="user-email" v-model="form.email" type="email" class="admin-input" dir="ltr" />
</div>
</div>
<div class="admin-form__field">
<label for="user-role">نقش</label>
<select id="user-role" v-model="form.role" class="admin-input">
<option value="admin">مدیر سیستم</option>
<option value="sales">واحد فروش</option>
<option value="finance">واحد مالی</option>
<option value="warehouse">انبار</option>
</select>
</div>
<div class="admin-form__field">
<BaseSwitch v-model="form.isActive" label="وضعیت" />
</div>
</form>
<template #footer>
<button type="button" class="admin-btn admin-btn--outline" @click="closeModal">انصراف</button>
<button type="button" class="admin-btn admin-btn--primary" :disabled="!isFormValid || saving" @click="submitUser">
<span v-if="saving" class="admin-btn__spinner" aria-hidden="true" />
{{ modalMode === 'edit' ? 'ذخیره تغییرات' : 'ثبت کاربر' }}
</button>
</template>
</BaseModal>
</div>
</template>
<style scoped>
.users-page__search {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
margin-bottom: 16px;
color: var(--color-text-muted);
}
.users-page__search-input {
flex: 1;
border: none;
background: transparent;
font-size: 0.875rem;
outline: none;
}
.users-page__table {
overflow: hidden;
}
</style>