This commit is contained in:
2026-04-18 11:33:21 +03:00
parent c4e113a494
commit af757ff224
11 changed files with 753 additions and 27 deletions

View File

@@ -0,0 +1,124 @@
<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">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 class="divide-y divide-gray-200">
<tr v-for="user in users" :key="user.id">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.login }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.ip || '-' }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ 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 hover:text-blue-800">Edit</button>
<button @click="deleteUser(user.id)" class="text-red-600 hover:text-red-800">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">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 users = ref([]);
const modalOpen = ref(false);
const modalMode = ref<'create' | 'edit'>('create');
const form = ref({ id: null, login: '', 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();
}
function openModal(mode: 'create' | 'edit', user: any = null) {
modalMode.value = mode;
if (mode === 'create') {
form.value = { id: null, login: '', password: '' };
modalTitle.value = 'Create User';
} else {
form.value = { id: user.id, login: user.login, password: '' };
modalTitle.value = 'Edit User';
}
modalOpen.value = true;
}
function closeModal() {
modalOpen.value = false;
}
async function submitUser() {
try {
if (modalMode.value === 'create') {
await fetch('/api/admin/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ login: form.value.login, password: form.value.password })
});
} else {
await fetch(`/api/admin/users/${form.value.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ login: form.value.login, password: form.value.password || undefined })
});
}
await loadUsers();
closeModal();
} catch (e) {
alert('Operation failed');
}
}
async function deleteUser(id: number) {
if (confirm('Are you sure?')) {
await fetch(`/api/admin/users/${id}`, { method: 'DELETE' });
await loadUsers();
}
}
onMounted(loadUsers);
</script>