ajustes
This commit is contained in:
@@ -10,7 +10,7 @@ interface ApiUser {
|
||||
IsActive: boolean
|
||||
}
|
||||
|
||||
const { data: usuarios, pending } = await useAsyncData('usuarios', () =>
|
||||
const { data: usuarios, refresh } = await useAsyncData('usuarios', () =>
|
||||
apiFetch<ApiUser[]>('/users')
|
||||
)
|
||||
|
||||
@@ -19,34 +19,185 @@ const roleLabel: Record<string, string> = {
|
||||
member: 'Membro',
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ id: 'Name', accessorKey: 'Name', header: 'Nome' },
|
||||
{ id: 'Email', accessorKey: 'Email', header: 'E-mail' },
|
||||
{ id: 'Role', accessorKey: 'Role', header: 'Perfil' },
|
||||
{ id: 'IsActive', accessorKey: 'IsActive', header: 'Status' },
|
||||
]
|
||||
// --- Modal Criar ---
|
||||
const showCreate = ref(false)
|
||||
const createForm = reactive({ name: '', email: '', password: '', role: 'member' })
|
||||
const createError = ref('')
|
||||
const createLoading = ref(false)
|
||||
|
||||
async function criarUsuario() {
|
||||
createError.value = ''
|
||||
createLoading.value = true
|
||||
try {
|
||||
await apiFetch('/users', {
|
||||
method: 'POST',
|
||||
body: { name: createForm.name, email: createForm.email, password: createForm.password, role: createForm.role },
|
||||
})
|
||||
showCreate.value = false
|
||||
createForm.name = ''
|
||||
createForm.email = ''
|
||||
createForm.password = ''
|
||||
createForm.role = 'member'
|
||||
await refresh()
|
||||
} catch (err: any) {
|
||||
createError.value = err?.data?.error || 'Erro ao criar usuário.'
|
||||
} finally {
|
||||
createLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --- Modal Editar ---
|
||||
const showEdit = ref(false)
|
||||
const editUser = ref<ApiUser | null>(null)
|
||||
const editForm = reactive({ name: '', role: 'member' })
|
||||
const editError = ref('')
|
||||
const editLoading = ref(false)
|
||||
|
||||
function abrirEditar(u: ApiUser) {
|
||||
editUser.value = u
|
||||
editForm.name = u.Name
|
||||
editForm.role = u.Role
|
||||
editError.value = ''
|
||||
showEdit.value = true
|
||||
}
|
||||
|
||||
async function salvarEdicao() {
|
||||
if (!editUser.value) return
|
||||
editError.value = ''
|
||||
editLoading.value = true
|
||||
try {
|
||||
await apiFetch(`/users/${editUser.value.ID}`, {
|
||||
method: 'PUT',
|
||||
body: { name: editForm.name, role: editForm.role },
|
||||
})
|
||||
showEdit.value = false
|
||||
await refresh()
|
||||
} catch (err: any) {
|
||||
editError.value = err?.data?.error || 'Erro ao salvar.'
|
||||
} finally {
|
||||
editLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --- Toggle status ---
|
||||
const togglingId = ref<string | null>(null)
|
||||
|
||||
async function toggleStatus(u: ApiUser) {
|
||||
togglingId.value = u.ID
|
||||
try {
|
||||
if (u.IsActive) {
|
||||
await apiFetch(`/users/${u.ID}`, { method: 'DELETE' })
|
||||
} else {
|
||||
const isActive = true
|
||||
await apiFetch(`/users/${u.ID}`, { method: 'PUT', body: { is_active: isActive } })
|
||||
}
|
||||
await refresh()
|
||||
} catch {}
|
||||
togglingId.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<AppTopbar title="Usuários" breadcrumb="Sistema · Usuários">
|
||||
<template #actions>
|
||||
<UButton size="sm" class="btn-primary">+ Novo Usuário</UButton>
|
||||
<UButton size="sm" class="btn-primary" @click="showCreate = true">+ Novo Usuário</UButton>
|
||||
</template>
|
||||
</AppTopbar>
|
||||
|
||||
<div class="content">
|
||||
<div class="card">
|
||||
<UTable :data="usuarios ?? []" :columns="columns" :loading="pending">
|
||||
<template #Role-cell="{ row }">{{ roleLabel[row.original.Role] ?? row.original.Role }}</template>
|
||||
<template #IsActive-cell="{ row }">
|
||||
<UBadge
|
||||
:label="row.original.IsActive ? 'Ativo' : 'Inativo'"
|
||||
:color="row.original.IsActive ? 'success' : 'neutral'"
|
||||
variant="soft"
|
||||
size="xs"
|
||||
/>
|
||||
</template>
|
||||
</UTable>
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>E-mail</th>
|
||||
<th>Perfil</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="u in (usuarios ?? [])" :key="u.ID">
|
||||
<td>{{ u.Name }}</td>
|
||||
<td class="email">{{ u.Email }}</td>
|
||||
<td>{{ roleLabel[u.Role] ?? u.Role }}</td>
|
||||
<td>
|
||||
<span :class="['badge', u.IsActive ? 'badge-active' : 'badge-inactive']">
|
||||
{{ u.IsActive ? 'Ativo' : 'Inativo' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="actions-cell">
|
||||
<button class="act-btn" @click="abrirEditar(u)">Editar</button>
|
||||
<button
|
||||
class="act-btn"
|
||||
:class="u.IsActive ? 'act-danger' : 'act-success'"
|
||||
:disabled="togglingId === u.ID"
|
||||
@click="toggleStatus(u)"
|
||||
>
|
||||
{{ u.IsActive ? 'Desativar' : 'Ativar' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!usuarios?.length">
|
||||
<td colspan="5" class="empty">Nenhum usuário encontrado.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Criar -->
|
||||
<div v-if="showCreate" class="overlay">
|
||||
<div class="modal">
|
||||
<h2>Novo Usuário</h2>
|
||||
<div class="field">
|
||||
<label>Nome</label>
|
||||
<UInput v-model="createForm.name" placeholder="Nome completo" class="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>E-mail</label>
|
||||
<UInput v-model="createForm.email" type="email" placeholder="email@empresa.com" class="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Senha</label>
|
||||
<UInput v-model="createForm.password" type="password" placeholder="Mínimo 8 caracteres" class="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Perfil</label>
|
||||
<select v-model="createForm.role" class="sel">
|
||||
<option value="member">Membro</option>
|
||||
<option value="admin">Administrador</option>
|
||||
</select>
|
||||
</div>
|
||||
<p v-if="createError" class="err">{{ createError }}</p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" @click="showCreate = false">Cancelar</button>
|
||||
<UButton class="btn-primary" size="sm" :loading="createLoading" @click="criarUsuario">Criar</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Editar -->
|
||||
<div v-if="showEdit" class="overlay">
|
||||
<div class="modal">
|
||||
<h2>Editar Usuário</h2>
|
||||
<div class="field">
|
||||
<label>Nome</label>
|
||||
<UInput v-model="editForm.name" class="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Perfil</label>
|
||||
<select v-model="editForm.role" class="sel">
|
||||
<option value="member">Membro</option>
|
||||
<option value="admin">Administrador</option>
|
||||
</select>
|
||||
</div>
|
||||
<p v-if="editError" class="err">{{ editError }}</p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" @click="showEdit = false">Cancelar</button>
|
||||
<UButton class="btn-primary" size="sm" :loading="editLoading" @click="salvarEdicao">Salvar</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,4 +208,38 @@ const columns = [
|
||||
.content { padding: 20px 22px; flex: 1; overflow-y: auto; }
|
||||
.card { background: white; border-radius: 11px; border: 1px solid #e2e8f0; overflow: hidden; }
|
||||
.btn-primary { background: linear-gradient(135deg, #667eea, #764ba2) !important; }
|
||||
|
||||
.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.tbl thead tr { background: #f8fafc; border-bottom: 1px solid #e2e8f0; }
|
||||
.tbl th { padding: 10px 14px; text-align: left; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.tbl td { padding: 12px 14px; border-bottom: 1px solid #f1f5f9; color: #1e293b; }
|
||||
.tbl tbody tr:last-child td { border-bottom: none; }
|
||||
.tbl tbody tr:hover { background: #fafafa; }
|
||||
.email { color: #64748b; }
|
||||
.empty { text-align: center; color: #94a3b8; padding: 32px !important; }
|
||||
|
||||
.badge { display: inline-block; padding: 2px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; }
|
||||
.badge-active { background: #dcfce7; color: #16a34a; }
|
||||
.badge-inactive { background: #f1f5f9; color: #94a3b8; }
|
||||
|
||||
.actions-cell { display: flex; gap: 6px; }
|
||||
.act-btn { font-size: 12px; font-weight: 500; padding: 4px 10px; border-radius: 6px; border: 1px solid #e2e8f0; background: white; cursor: pointer; color: #667eea; transition: all 0.15s; }
|
||||
.act-btn:hover { background: #f0f3ff; border-color: #667eea; }
|
||||
.act-danger { color: #dc2626; }
|
||||
.act-danger:hover { background: #fff1f1; border-color: #dc2626; }
|
||||
.act-success { color: #16a34a; }
|
||||
.act-success:hover { background: #dcfce7; border-color: #16a34a; }
|
||||
.act-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* Modal */
|
||||
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; z-index: 100; }
|
||||
.modal { background: white; border-radius: 14px; padding: 28px; width: 100%; max-width: 400px; box-shadow: 0 20px 50px rgba(0,0,0,0.2); }
|
||||
.modal h2 { font-size: 16px; font-weight: 700; color: #0f172a; margin-bottom: 18px; }
|
||||
.field { margin-bottom: 14px; }
|
||||
.field label { display: block; font-size: 12px; font-weight: 600; color: #475569; margin-bottom: 5px; }
|
||||
.sel { width: 100%; padding: 8px 10px; border: 1px solid #e2e8f0; border-radius: 8px; font-size: 13px; color: #0f172a; background: white; }
|
||||
.err { color: #dc2626; font-size: 12px; margin-bottom: 10px; }
|
||||
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
|
||||
.btn-cancel { font-size: 13px; padding: 6px 14px; border-radius: 8px; border: 1px solid #e2e8f0; background: white; cursor: pointer; color: #64748b; }
|
||||
.btn-cancel:hover { background: #f8fafc; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user