183 lines
6.5 KiB
Vue
183 lines
6.5 KiB
Vue
<template>
|
|
<AppLayout>
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h1 class="text-2xl font-bold text-gray-900">Users Management</h1>
|
|
<button @click="openModal('create')" class="btn-primary">+ Add User</button>
|
|
</div>
|
|
|
|
<div class="card overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Login</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Active</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">IP</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Created</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="user in users" :key="user.id">
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.id }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.login }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.email }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
|
<button
|
|
v-if="user.id !== currentUserId"
|
|
@click="toggleActive(user)"
|
|
:class="user.active ? 'text-green-600' : 'text-red-600'"
|
|
>
|
|
{{ user.active ? 'Active' : 'Inactive' }}
|
|
</button>
|
|
<span v-else class="text-gray-400 text-sm">(You)</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.ip || '-' }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ formatDate(user.created) }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm space-x-2">
|
|
<button @click="openModal('edit', user)" class="text-blue-600">Edit</button>
|
|
<button
|
|
v-if="user.id !== currentUserId"
|
|
@click="deleteUser(user.id)"
|
|
class="text-red-600"
|
|
>
|
|
Delete
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div v-if="modalOpen" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
|
<h2 class="text-xl font-bold mb-4">{{ modalTitle }}</h2>
|
|
<form @submit.prevent="submitUser">
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700">Email</label>
|
|
<input v-model="form.email" type="text" required class="input-field mt-1" />
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700">Login</label>
|
|
<input v-model="form.login" type="text" required class="input-field mt-1" />
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700">Password</label>
|
|
<input v-model="form.password" :required="modalMode === 'create'" type="password" class="input-field mt-1" />
|
|
<p v-if="modalMode === 'edit'" class="text-xs text-gray-500 mt-1">Leave blank to keep current password</p>
|
|
</div>
|
|
<div class="flex justify-end space-x-2">
|
|
<button type="button" @click="closeModal" class="btn-secondary">Cancel</button>
|
|
<button type="submit" class="btn-primary">Save</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue';
|
|
import AppLayout from '../components/Layout/AppLayout.vue';
|
|
|
|
const currentUserId = ref<number | null>(null);
|
|
|
|
async function loadCurrentUser() {
|
|
try {
|
|
const res = await fetch('/api/admin/me');
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
currentUserId.value = data.id;
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to load current user', e);
|
|
}
|
|
}
|
|
|
|
const users = ref([]);
|
|
const modalOpen = ref(false);
|
|
const modalMode = ref<'create' | 'edit'>('create');
|
|
const form = ref({ id: null, login: '', email: '', password: '' });
|
|
const modalTitle = ref('');
|
|
|
|
async function loadUsers() {
|
|
const res = await fetch('/api/admin/users');
|
|
users.value = await res.json();
|
|
}
|
|
|
|
function formatDate(dateStr: string) {
|
|
if (!dateStr) return '-';
|
|
return new Date(dateStr).toLocaleString();
|
|
}
|
|
|
|
async function toggleActive(user: any) {
|
|
await fetch(`/api/admin/users/${user.id}/activate?active=${!user.active}`, { method: 'PUT' })
|
|
await loadUsers()
|
|
}
|
|
|
|
function openModal(mode: 'create' | 'edit', user: any = null) {
|
|
modalMode.value = mode;
|
|
if (mode === 'create') {
|
|
form.value = { id: null, login: '', email: '', password: '' };
|
|
modalTitle.value = 'Create User';
|
|
} else {
|
|
form.value = { id: user.id, login: user.login, email: user.email, password: '' }; // добавлен email
|
|
modalTitle.value = 'Edit User';
|
|
}
|
|
modalOpen.value = true;
|
|
}
|
|
|
|
function closeModal() {
|
|
modalOpen.value = false;
|
|
}
|
|
|
|
async function submitUser() {
|
|
try {
|
|
const payload: any = {
|
|
login: form.value.login,
|
|
email: form.value.email,
|
|
};
|
|
if (form.value.password) {
|
|
payload.password = form.value.password;
|
|
}
|
|
if (modalMode.value === 'create') {
|
|
if (!form.value.password) {
|
|
alert('Password is required');
|
|
return;
|
|
}
|
|
const res = await fetch('/api/admin/users', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!res.ok) throw new Error('Create failed');
|
|
} else {
|
|
const res = await fetch(`/api/admin/users/${form.value.id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!res.ok) throw new Error('Update failed');
|
|
}
|
|
await loadUsers();
|
|
closeModal();
|
|
} catch (e) {
|
|
alert('Operation failed: ' + e.message);
|
|
}
|
|
}
|
|
|
|
async function deleteUser(id: number) {
|
|
if (confirm('Are you sure?')) {
|
|
await fetch(`/api/admin/users/${id}`, { method: 'DELETE' });
|
|
await loadUsers();
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await loadCurrentUser();
|
|
await loadUsers();
|
|
});
|
|
</script>
|