Compare commits

...

2 Commits

Author SHA1 Message Date
413e2ff9cd Kilistázást
A kilistázása az API [GET] /contacts API hívásnak
2025-10-24 22:08:39 +02:00
2b0fd46b7f Alap oldalak és menüsor 2025-10-24 21:23:25 +02:00

View File

@@ -3,39 +3,33 @@
<div v-if="pageStatus=='logout'" class="flex flex-col w-full h-full"> <div v-if="pageStatus=='logout'" class="flex flex-col w-full h-full">
<div class="flex flex-row-reverse font-serif p-2"> <div class="flex flex-row-reverse font-serif p-2">
<div class="flex">API Status: <div class="flex">API Status:
<svg v-if="loginCheck" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-green-500"> <svg v-if="loginCheck" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-green-500">
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
</svg> </svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-red-500"> <svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-red-500">
<path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
</svg> </svg>
</div> </div>
<div class="flex mr-5">API Info: {{ apiBase }} </div> <div class="flex mr-5">API Info: {{ apiBase }} </div>
</div> </div>
<div class="flex w-full h-full justify-center items-center">
<div class="flex w-full h-full justify-center items-center">
<div class="flex flex-col w-1/4 h-max bg-stone-300 p-4 rounded-xl"> <div class="flex flex-col w-1/4 h-max bg-stone-300 p-4 rounded-xl">
<div class="flex flex-row font-serif text-2xl">{{loginErrorMsg}}</div> <div class="flex flex-row font-serif text-2xl">{{loginErrorMsg}}</div>
<hr class="flex border-2 border-black mb-5"> <hr class="flex border-2 border-black mb-5">
<input v-model="userLoginName" type="text" class="flex p-2 mb-1 font-serif" placeholder="Felhasználónév"> <input v-model="userLoginName" type="text" class="flex p-2 mb-1 font-serif" placeholder="Felhasználónév">
<input v-model="userLoginPw" type="password" class="flex p-2 font-serif" :class="{hidden: isLoading}" placeholder="Jelszó"> <input v-model="userLoginPw" type="password" class="flex p-2 font-serif" :class="{hidden: isLoading}" placeholder="Jelszó">
<div class="flex flex-row-reverse w-full h-max" > <div class="flex flex-row-reverse w-full h-max" >
<input @click="loginUser" type="submit" value="Login" class="flex bg-neutral-500 text-white mt-5 px-3 py-1 rounded-xl w-min"></input> <input @click="loginUser" type="submit" value="Login" class="flex bg-neutral-500 text-white mt-5 px-3 py-1 rounded-xl w-min">
</div>
</div>
</div> </div>
</div> </div>
</div>
</div>
<!-- Dashboard oldal nézet --> <!-- Dashboard oldal nézet -->
<div v-else-if="pageStatus=='contacts'" class="flex flex-row w-full h-full bg-red-300"> <div v-else-if="pageStatus=='contacts'" class="flex flex-row w-full h-full bg-red-300">
<!-- Menü --> <!-- Menü -->
<div class="flex flex-col w-1/6 h-full bg-neutral-400 py-10 px-3 space-y-3"> <div class="flex flex-col w-1/6 h-full bg-neutral-400 py-10 px-3 space-y-3">
<!-- Menü Item -->
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between"> <div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between">
<div class="flex h-min w-fit ">Telefonkönyv</div> <div class="flex h-min w-fit ">Telefonkönyv</div>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
@@ -43,24 +37,21 @@
</svg> </svg>
</div> </div>
<!-- Menü Item --> <div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between opacity-60">
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between">
<div class="flex h-min w-fit ">Admin</div> <div class="flex h-min w-fit ">Admin</div>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
</svg> </svg>
</div> </div>
<!-- Menü Item -->
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between"> <div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between">
<div class="flex h-min w-fit ">Kijelentkezés</div> <button class="flex h-min w-fit" @click="logout">Kijelentkezés</button>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" /> <path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
</svg> </svg>
</div> </div>
</div> </div>
<!-- Oldal --> <!-- Oldal -->
<div class="flex flex-col w-full h-full bg-neutral-200"> <div class="flex flex-col w-full h-full bg-neutral-200">
<div class="flex px-20 pt-10 pb-5 font-[tahoma] text-5xl"> {{ menuSel }} </div> <div class="flex px-20 pt-10 pb-5 font-[tahoma] text-5xl"> {{ menuSel }} </div>
@@ -68,7 +59,78 @@
<!-- Contents --> <!-- Contents -->
<div class="flex flex-col bg-neutral-50 mx-20 py-5 px-2 h-full"> <div class="flex flex-col bg-neutral-50 mx-20 py-5 px-2 h-full">
asd
<!-- TELEFONKÖNYV TÁBLA -->
<div class="flex flex-row items-center justify-between mb-3">
<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>
<span v-if="isLoadingContacts" 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">Név</th>
<th class="py-2 px-3">Telefon</th>
<th class="py-2 px-3">Cím</th>
<th class="py-2 px-3">Megjegyzés</th>
<th class="py-2 px-3 w-24 text-center">Művelet</th>
</tr>
</thead>
<tbody>
<tr v-for="c in contacts" :key="c.id" class="border-t hover:bg-neutral-50">
<td class="py-2 px-3 text-neutral-500">{{ c.id }}</td>
<!-- Név -->
<td class="py-2 px-3" @dblclick="startEdit(c,'name')">
<input v-if="isEditing(c,'name')" v-model="editBuffer.name"
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
class="w-full px-2 py-1 border rounded-lg outline-none">
<span v-else>{{ c.name }}</span>
</td>
<!-- Telefon -->
<td class="py-2 px-3" @dblclick="startEdit(c,'phone')">
<input v-if="isEditing(c,'phone')" v-model="editBuffer.phone"
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
class="w-full px-2 py-1 border rounded-lg outline-none">
<span v-else>{{ c.phone }}</span>
</td>
<!-- Cím -->
<td class="py-2 px-3" @dblclick="startEdit(c,'address')">
<input v-if="isEditing(c,'address')" v-model="editBuffer.address"
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
class="w-full px-2 py-1 border rounded-lg outline-none">
<span v-else>{{ c.address }}</span>
</td>
<!-- Megjegyzés -->
<td class="py-2 px-3" @dblclick="startEdit(c,'note')">
<input v-if="isEditing(c,'note')" v-model="editBuffer.note"
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
class="w-full px-2 py-1 border rounded-lg outline-none">
<span v-else>{{ c.note }}</span>
</td>
<!-- Törlés -->
<td class="py-2 px-3 text-center">
<button @click="deleteContact(c.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="contacts.length===0 && !isLoadingContacts">
<td colspan="6" class="py-6 text-center text-neutral-500">Nincs megjeleníthető kontakt.</td>
</tr>
</tbody>
</table>
</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> </div>
</div> </div>
</div> </div>
@@ -84,59 +146,148 @@ const loginCheck = ref(false)
const loginErrorMsg = ref("Bejelentkezés...") const loginErrorMsg = ref("Bejelentkezés...")
const userLoginName = ref("") const userLoginName = ref("")
const userLoginPw = ref("") const userLoginPw = ref("")
const userName = ref("")
const userPassw = ref("")
const userAdmin = ref(false) const userAdmin = ref(false)
const isLoading = ref(false) const isLoading = ref(false)
const menuSel = ref("Telefonkönyv") // Telefonkönyv, Admin, Kijelentkezés const menuSel = ref("Telefonkönyv") // Telefonkönyv, Admin, Kijelentkezés
// --- Telefonkönyv állapot ---
const contacts = ref([])
const isLoadingContacts = ref(false)
const editing = ref({ id: null, field: null })
const editBuffer = ref({ id: null, name: "", phone: "", address: "", note: "" })
// ---- Segédfüggvény: GET body workaround ----
// Browserek nem küldenek body-t GET-tel, ezért POST-tal kérjük és
// X-HTTP-Method-Override: GET fejlécet adunk hozzá.
async function getWithBody(url, bodyObj) {
const res = await fetch(url, {
method: 'POST',
headers: { 'X-HTTP-Method-Override': 'GET', 'Content-Type': 'application/json' },
body: JSON.stringify(bodyObj)
})
return res
}
// --- API hívások a /contacts-hoz ---
async function loadContacts() {
isLoadingContacts.value = true
console.log(`${apiBase}/contacts`)
try {
const res = await getWithBody(`${apiBase}/contacts`, {
authUname: userLoginName.value,
authPw: userLoginPw.value
})
const data = await res.json()
if (data.ok) contacts.value = data.contacts || []
else contacts.value = []
} catch (e) {
console.error(e)
contacts.value = []
} finally {
isLoadingContacts.value = false
}
}
async function patchContact(id, patch) {
const res = await fetch(`${apiBase}/contacts`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
authUname: userLoginName.value,
authPw: userLoginPw.value,
id,
...patch
})
})
return res.json()
}
async function deleteContact(id) {
if (!confirm(`Biztosan törlöd a(z) #${id} kontaktot?`)) return
const res = await fetch(`${apiBase}/contacts`, {
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) {
contacts.value = contacts.value.filter(c => c.id !== id)
}
}
// --- Inline edit kezelése ---
function startEdit(row, field) {
editing.value = { id: row.id, field }
editBuffer.value = { ...row }
}
function isEditing(row, field) {
return editing.value.id === row.id && editing.value.field === field
}
async function commitEdit() {
if (!editing.value.id) return
const { id, field } = editing.value
const value = editBuffer.value[field]
const data = await patchContact(id, { [field]: value })
if (data.ok) {
// lokális frissítés
const idx = contacts.value.findIndex(c => c.id === id)
if (idx !== -1) contacts.value[idx][field] = value
}
editing.value = { id: null, field: null }
}
function cancelEdit() {
editing.value = { id: null, field: null }
}
// --- Alap (health + auth) ---
async function checkHealth() { async function checkHealth() {
out.value = 'Hívás...' out.value = 'Hívás...'
const res = await fetch(`${apiBase}/`) const res = await fetch(`${apiBase}/`)
out.value = await res.json() out.value = await res.json()
console.log(out.value)
} }
async function authUser(u, p) { async function authUser(u, p) {
out.value = 'Hívás...'
const res = await fetch(`${apiBase}/auth`, { const res = await fetch(`${apiBase}/auth`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ authUname: u, authPw: p }) body: JSON.stringify({ authUname: u, authPw: p })
}) })
out.value = await res.json() const data = await res.json()
console.log(out.value) if (data.ok === true) {
if (out.value.ok == true) {
pageStatus.value = "contacts" pageStatus.value = "contacts"
userAdmin.value = out.value.admin userAdmin.value = data.admin
await loadContacts()
loginErrorMsg.value = "Sikeres bejelentkezés"
} else { } else {
loginErrorMsg.value = "Hiba..." loginErrorMsg.value = "Hibás felhasználónév vagy jelszó"
} }
} }
async function loginUser() { async function loginUser() {
isLoading.value = true isLoading.value = true
console.log(`Authenticateing user ${userLoginName.value}`) await authUser(userLoginName.value, userLoginPw.value)
authUser(userLoginName.value, userLoginPw.value)
isLoading.value = false isLoading.value = false
} }
// async IIFE function logout() {
(async () => { pageStatus.value = "logout"
out.value = "" contacts.value = []
await checkHealth() userAdmin.value = false
if (out.value.status == "ok") { userLoginPw.value = ""
loginCheck.value = true loginErrorMsg.value = "Bejelentkezés..."
console.log("[API] ok")
}
else {
loginCheck.value = false
console.log("[API] not ok")
} }
})(); // async IIFE
;(async () => {
await checkHealth()
loginCheck.value = out.value?.status === "ok"
console.log(loginCheck.value ? "[API] ok" : "[API] not ok")
})()
</script> </script>