Frontend kész
Mostmár minden API Endpoint le van implementálva
This commit is contained in:
337
src/App.vue
337
src/App.vue
@@ -57,11 +57,41 @@
|
||||
<div class="flex px-20 pt-10 pb-5 font-[tahoma] text-5xl"> {{ menuSel }} </div>
|
||||
<hr class="flex border-2 border-black ml-20 mb-6">
|
||||
|
||||
<!-- Contents -->
|
||||
<!-- Contacts -->
|
||||
<div v-if="pageStatus == 'contacts'" class="flex flex-col bg-neutral-50 mx-20 py-5 px-2 h-full">
|
||||
|
||||
<!-- ÚJ KONTAKT LÉTREHOZÁSA -->
|
||||
<form @submit.prevent="createContact" class="flex items-start flex-wrap gap-2 mb-4">
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm text-neutral-600">Név*</label>
|
||||
<input v-model.trim="newContact.name" class="px-2 py-1 border rounded-lg" placeholder="Teszt Elek" required>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm text-neutral-600">Telefon*</label>
|
||||
<input v-model.trim="newContact.phone" class="px-2 py-1 border rounded-lg" placeholder="+36 30 123 4567" required>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm text-neutral-600">Cím</label>
|
||||
<input v-model.trim="newContact.address" class="px-2 py-1 border rounded-lg" placeholder="Budapest">
|
||||
</div>
|
||||
<div class="flex flex-col grow">
|
||||
<label class="text-sm text-neutral-600">Megjegyzés</label>
|
||||
<input v-model.trim="newContact.note" class="px-2 py-1 border rounded-lg" placeholder="VIP">
|
||||
</div>
|
||||
|
||||
<button
|
||||
:disabled="isCreating || !newContact.name || !newContact.phone"
|
||||
class="px-3 py-2 rounded-lg bg-emerald-600 text-white disabled:opacity-50">
|
||||
Hozzáadás
|
||||
</button>
|
||||
|
||||
<span v-if="createMsg" class="text-sm ml-1"
|
||||
:class="createOk ? 'text-emerald-700' : 'text-red-600'">{{ createMsg }}</span>
|
||||
</form>
|
||||
|
||||
|
||||
<!-- TELEFONKÖNYV TÁBLA -->
|
||||
<div class="flex flex-row items-center justify-between mb-3">
|
||||
<div class="flex flex-row items-center justify-between mb-3 mt-10">
|
||||
<div class="text-lg font-medium">Kontaktok</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="loadContacts" class="px-3 py-1 rounded-lg bg-neutral-800 text-white">Frissítés</button>
|
||||
@@ -132,6 +162,117 @@
|
||||
</div>
|
||||
<div class="text-xs text-neutral-500 mt-2">Tipp: duplán kattints egy mezőre a szerkesztéshez. Enter ment, Esc mégse.</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin -->
|
||||
<div v-if="pageStatus == 'admin'" class="flex flex-col bg-neutral-50 mx-20 py-5 px-2 h-full">
|
||||
|
||||
<!-- ÚJ FELHASZNÁLÓ LÉTREHOZÁSA -->
|
||||
<form @submit.prevent="createUser" class="flex items-start flex-wrap gap-2 mb-4">
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm text-neutral-600">Felhasználónév*</label>
|
||||
<input v-model.trim="newUser.uname" class="px-2 py-1 border rounded-lg" placeholder="ujuser" required>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<label class="text-sm text-neutral-600">Jelszó*</label>
|
||||
<input v-model.trim="newUser.pw" type="password" class="px-2 py-1 border rounded-lg" placeholder="••••••" required>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-6 ml-2">
|
||||
<input id="newIsAdmin" type="checkbox" v-model="newUser.admin" class="w-4 h-4">
|
||||
<label for="newIsAdmin" class="text-sm text-neutral-700">Admin</label>
|
||||
</div>
|
||||
<div class="flex flex-col grow">
|
||||
<label class="text-sm text-neutral-600">Megjegyzés</label>
|
||||
<input v-model.trim="newUser.note" class="px-2 py-1 border rounded-lg" placeholder="Megjegyzés">
|
||||
</div>
|
||||
|
||||
<button
|
||||
:disabled="isCreatingUser || !newUser.uname || !newUser.pw"
|
||||
class="px-3 py-2 rounded-lg bg-emerald-600 text-white disabled:opacity-50">
|
||||
Hozzáadás
|
||||
</button>
|
||||
|
||||
<span v-if="createUserMsg" class="text-sm ml-1"
|
||||
:class="createUserOk ? 'text-emerald-700' : 'text-red-600'">{{ createUserMsg }}</span>
|
||||
</form>
|
||||
|
||||
<!-- USERS TÁBLA -->
|
||||
<div class="flex flex-row items-center justify-between mb-3 mt-4">
|
||||
<div class="text-lg font-medium">Felhasználók</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="loadUsers" class="px-3 py-1 rounded-lg bg-neutral-800 text-white">Frissítés</button>
|
||||
<span v-if="isLoadingUsers" class="text-sm opacity-70">Töltés…</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-auto border border-neutral-200 rounded-xl">
|
||||
<table class="min-w-full text-left">
|
||||
<thead class="bg-neutral-100">
|
||||
<tr class="text-sm text-neutral-700">
|
||||
<th class="py-2 px-3 w-10">ID</th>
|
||||
<th class="py-2 px-3">Felhasználónév</th>
|
||||
<th class="py-2 px-3">Admin</th>
|
||||
<th class="py-2 px-3">Megjegyzés</th>
|
||||
<th class="py-2 px-3">Jelszó (új)</th>
|
||||
<th class="py-2 px-3 w-24 text-center">Művelet</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="u in users" :key="u.id" class="border-t hover:bg-neutral-50">
|
||||
<td class="py-2 px-3 text-neutral-500">{{ u.id }}</td>
|
||||
|
||||
<!-- uname -->
|
||||
<td class="py-2 px-3" @dblclick="startUserEdit(u,'uname')">
|
||||
<input v-if="isUserEditing(u,'uname')" v-model="userEditBuffer.uname"
|
||||
@keyup.enter="commitUserEdit" @keyup.esc="cancelUserEdit" @blur="commitUserEdit"
|
||||
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||
<span v-else>{{ u.uname }}</span>
|
||||
</td>
|
||||
|
||||
<!-- admin -->
|
||||
<td class="py-2 px-3" @dblclick="startUserEdit(u,'admin')">
|
||||
<template v-if="isUserEditing(u,'admin')">
|
||||
<input type="checkbox" v-model="userEditBuffer.admin" @change="commitUserEdit" @keyup.esc="cancelUserEdit" class="w-4 h-4">
|
||||
<span class="ml-2 uppercase">{{ userEditBuffer.admin ? 'igen' : 'nem' }}</span>
|
||||
</template>
|
||||
<span v-else class="uppercase">{{ u.admin ? 'igen' : 'nem' }}</span>
|
||||
</td>
|
||||
|
||||
<!-- note -->
|
||||
<td class="py-2 px-3" @dblclick="startUserEdit(u,'note')">
|
||||
<input v-if="isUserEditing(u,'note')" v-model="userEditBuffer.note"
|
||||
@keyup.enter="commitUserEdit" @keyup.esc="cancelUserEdit" @blur="commitUserEdit"
|
||||
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||
<span v-else>{{ u.note }}</span>
|
||||
</td>
|
||||
|
||||
<!-- pw reset -->
|
||||
<td class="py-2 px-3" @dblclick="startUserEdit(u,'pw')">
|
||||
<input v-if="isUserEditing(u,'pw')" v-model="userEditBuffer.pw" type="password" placeholder="Új jelszó"
|
||||
@keyup.enter="commitUserEdit" @keyup.esc="cancelUserEdit" @blur="commitUserEdit"
|
||||
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||
<span v-else class="text-neutral-400 italic">—</span>
|
||||
</td>
|
||||
|
||||
<!-- törlés -->
|
||||
<td class="py-2 px-3 text-center">
|
||||
<button @click="deleteUser(u.id)"
|
||||
class="px-3 py-1 rounded-lg bg-red-600 text-white hover:bg-red-700">Törlés</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="users.length===0 && !isLoadingUsers">
|
||||
<td colspan="6" class="py-6 text-center text-neutral-500">Nincs felhasználó.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-neutral-500 mt-2">
|
||||
Tipp: duplán katt egy mezőre a szerkesztéshez. A „Jelszó (új)” oszlopban megadott érték azonnal új jelszó lesz.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -156,6 +297,198 @@ const isLoadingContacts = ref(false)
|
||||
const editing = ref({ id: null, field: null })
|
||||
const editBuffer = ref({ id: null, name: "", phone: "", address: "", note: "" })
|
||||
|
||||
// Új kontakt űrlap state
|
||||
const newContact = ref({ name: '', phone: '', address: '', note: '' })
|
||||
const isCreating = ref(false)
|
||||
const createMsg = ref('')
|
||||
const createOk = ref(false)
|
||||
|
||||
// --- Admin / users állapot ---
|
||||
const users = ref([])
|
||||
const isLoadingUsers = ref(false)
|
||||
const userEditing = ref({ id: null, field: null })
|
||||
const userEditBuffer = ref({ id: null, uname: '', admin: false, note: '', pw: '' })
|
||||
|
||||
// Új user űrlap
|
||||
const newUser = ref({ uname: '', pw: '', admin: false, note: '' })
|
||||
const isCreatingUser = ref(false)
|
||||
const createUserMsg = ref('')
|
||||
const createUserOk = ref(false)
|
||||
|
||||
async function loadUsers() {
|
||||
if (!userAdmin.value) return
|
||||
isLoadingUsers.value = true
|
||||
try {
|
||||
const res = await getWithBody(`${apiBase}/users`, {
|
||||
authUname: userLoginName.value,
|
||||
authPw: userLoginPw.value
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
users.value = (data.users || []).map(u => ({ ...u, admin: !!u.admin }))
|
||||
} else {
|
||||
users.value = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
users.value = []
|
||||
} finally {
|
||||
isLoadingUsers.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createUser() {
|
||||
if (!newUser.value.uname || !newUser.value.pw) return
|
||||
isCreatingUser.value = true
|
||||
createUserMsg.value = ''
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/users`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
authUname: userLoginName.value,
|
||||
authPw: userLoginPw.value,
|
||||
uname: newUser.value.uname,
|
||||
pw: newUser.value.pw,
|
||||
admin: !!newUser.value.admin,
|
||||
note: newUser.value.note || null
|
||||
})
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
createUserOk.value = true
|
||||
createUserMsg.value = 'Felhasználó hozzáadva'
|
||||
users.value.unshift({
|
||||
id: data.id,
|
||||
uname: newUser.value.uname,
|
||||
admin: !!newUser.value.admin,
|
||||
note: newUser.value.note || null
|
||||
})
|
||||
newUser.value = { uname: '', pw: '', admin: false, note: '' }
|
||||
} else {
|
||||
createUserOk.value = false
|
||||
createUserMsg.value = data.error || 'Hiba történt'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
createUserOk.value = false
|
||||
createUserMsg.value = 'Hálózati hiba'
|
||||
} finally {
|
||||
isCreatingUser.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function patchUser(id, patch) {
|
||||
const res = await fetch(`${apiBase}/users`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
authUname: userLoginName.value,
|
||||
authPw: userLoginPw.value,
|
||||
id,
|
||||
...patch
|
||||
})
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
|
||||
async function deleteUser(id) {
|
||||
if (!confirm(`Biztosan törlöd a(z) #${id} felhasználót?`)) return
|
||||
const res = await fetch(`${apiBase}/users`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
authUname: userLoginName.value,
|
||||
authPw: userLoginPw.value,
|
||||
id
|
||||
})
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
users.value = users.value.filter(u => u.id !== id)
|
||||
}
|
||||
}
|
||||
|
||||
function startUserEdit(row, field) {
|
||||
userEditing.value = { id: row.id, field }
|
||||
userEditBuffer.value = { id: row.id, uname: row.uname, admin: !!row.admin, note: row.note ?? '', pw: '' }
|
||||
}
|
||||
|
||||
function isUserEditing(row, field) {
|
||||
return userEditing.value.id === row.id && userEditing.value.field === field
|
||||
}
|
||||
|
||||
async function commitUserEdit() {
|
||||
if (!userEditing.value.id) return
|
||||
const { id, field } = userEditing.value
|
||||
let val = userEditBuffer.value[field]
|
||||
|
||||
// speciális esetek
|
||||
if (field === 'admin') val = !!val
|
||||
if (field === 'pw') {
|
||||
if (!val) { cancelUserEdit(); return }
|
||||
const res = await patchUser(id, { pw: val })
|
||||
if (res.ok) userEditBuffer.value.pw = ''
|
||||
userEditing.value = { id: null, field: null }
|
||||
return
|
||||
}
|
||||
|
||||
const res = await patchUser(id, { [field]: val })
|
||||
if (res.ok) {
|
||||
const idx = users.value.findIndex(u => u.id === id)
|
||||
if (idx !== -1) users.value[idx][field] = val
|
||||
}
|
||||
userEditing.value = { id: null, field: null }
|
||||
}
|
||||
|
||||
function cancelUserEdit() {
|
||||
userEditing.value = { id: null, field: null }
|
||||
userEditBuffer.value.pw = ''
|
||||
}
|
||||
|
||||
|
||||
async function createContact () {
|
||||
if (!newContact.value.name || !newContact.value.phone) return
|
||||
isCreating.value = true
|
||||
createMsg.value = ''
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/contacts`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
authUname: userLoginName.value,
|
||||
authPw: userLoginPw.value,
|
||||
name: newContact.value.name,
|
||||
phone: newContact.value.phone,
|
||||
address: newContact.value.address || null,
|
||||
note: newContact.value.note || null
|
||||
})
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
createOk.value = true
|
||||
createMsg.value = 'Hozzáadva'
|
||||
contacts.value.unshift({
|
||||
id: data.id,
|
||||
name: newContact.value.name,
|
||||
phone: newContact.value.phone,
|
||||
address: newContact.value.address || null,
|
||||
note: newContact.value.note || null
|
||||
})
|
||||
newContact.value = { name: '', phone: '', address: '', note: '' }
|
||||
} else {
|
||||
createOk.value = false
|
||||
createMsg.value = data.error || 'Hiba történt'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
createOk.value = false
|
||||
createMsg.value = 'Hálózati hiba'
|
||||
} finally {
|
||||
isCreating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function switchAdmin(){
|
||||
pageStatus.value = "admin"
|
||||
menuSel.value = "Admin"
|
||||
|
||||
Reference in New Issue
Block a user