Skip to content

Commit d25eacb

Browse files
committed
Updated
1 parent 156210f commit d25eacb

3 files changed

Lines changed: 512 additions & 0 deletions

File tree

‎project/app.js‎

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import axios from "https://cdn.jsdelivr.net/npm/axios@1.6.7/dist/axios.min.js";
2+
3+
// --- Ayarlar ---
4+
axios.defaults.baseURL = "https://jsonplaceholder.typicode.com";
5+
const POSTS_LIMIT = 5;
6+
let currentPage = 1;
7+
8+
// --- HTML Elemanları ---
9+
const userList = document.querySelector("#user-list");
10+
const postList = document.querySelector("#post-list");
11+
const loadUsersBtn = document.querySelector("#load-users");
12+
const loadMoreBtn = document.querySelector("#load-more");
13+
const postForm = document.querySelector("#post-form");
14+
15+
// --- Bildirim Fonksiyonu ---
16+
const notify = (type, message) => {
17+
iziToast[type]({
18+
title: type === "success" ? "Başarılı" : "Hata",
19+
message: message,
20+
position: "topRight",
21+
timeout: 3000,
22+
});
23+
};
24+
25+
// --- Kullanıcı İşlemleri ---
26+
async function loadUsers() {
27+
try {
28+
// Destructuring: res.data yerine { data } alıyoruz
29+
const { data: users } = await axios.get("/users");
30+
renderUsers(users);
31+
notify("success", "Kullanıcılar yüklendi!");
32+
} catch (err) {
33+
console.error(err);
34+
notify("error", "Kullanıcılar yüklenemedi.");
35+
}
36+
}
37+
38+
function renderUsers(users) {
39+
userList.innerHTML = users
40+
.map((user) => {
41+
// Destructuring: Nesne özelliklerini çıkarıyoruz
42+
const { name, email, company, website } = user;
43+
return `
44+
<div class="user-card">
45+
<h4>${name}</h4>
46+
<p>📧 ${email}</p>
47+
<p>🏢 ${company.name}</p>
48+
<p>🌐 ${website}</p>
49+
</div>
50+
`;
51+
})
52+
.join("");
53+
}
54+
55+
loadUsersBtn.addEventListener("click", loadUsers);
56+
57+
// --- Gönderi İşlemleri ---
58+
59+
// 1. Gönderileri Getir (Async/Await + Params)
60+
async function loadPosts() {
61+
try {
62+
const { data: posts } = await axios.get("/posts", {
63+
params: { _limit: POSTS_LIMIT, _page: currentPage },
64+
});
65+
66+
if (posts.length === 0) {
67+
notify("info", "Daha fazla gönderi yok.");
68+
loadMoreBtn.style.display = "none";
69+
return;
70+
}
71+
72+
renderPosts(posts);
73+
currentPage++;
74+
} catch (err) {
75+
console.error(err);
76+
notify("error", "Gönderiler alınamadı.");
77+
}
78+
}
79+
80+
function renderPosts(posts) {
81+
const html = posts
82+
.map((post) => {
83+
const { id, title, body } = post; // Destructuring
84+
// Olay Delegasyonu için data-id ve data-action özniteliklerini ekliyoruz
85+
// Inline onclick (onclick="...") KULLANMIYORUZ
86+
return `
87+
<div class="post-card" id="post-${id}">
88+
<div class="post-content">
89+
<h4>${title}</h4>
90+
<p>${body}</p>
91+
</div>
92+
<div class="post-actions">
93+
<button class="btn primary btn-sm" data-action="edit" data-id="${id}">Düzenle</button>
94+
<button class="btn danger btn-sm" data-action="delete" data-id="${id}">Sil</button>
95+
</div>
96+
</div>
97+
`;
98+
})
99+
.join("");
100+
101+
postList.insertAdjacentHTML("beforeend", html);
102+
}
103+
104+
loadMoreBtn.addEventListener("click", loadPosts);
105+
106+
// 2. Yeni Gönderi Ekle (Form Submit + Destructuring)
107+
postForm.addEventListener("submit", async (e) => {
108+
e.preventDefault();
109+
const formData = new FormData(e.target);
110+
111+
// Form verilerini alırken trim() metodu kullanıyoruz (String metotları konusu)
112+
const title = formData.get("title").trim();
113+
const body = formData.get("body").trim();
114+
115+
if (!title || !body) {
116+
notify("warning", "Lütfen tüm alanları doldurun.");
117+
return;
118+
}
119+
120+
const newPost = { title, body, userId: 1 };
121+
122+
try {
123+
const { data } = await axios.post("/posts", newPost, {
124+
headers: { "Content-Type": "application/json" },
125+
});
126+
127+
// Spread Syntax (...) ile nesne kopyalama
128+
const fakePostWithId = { ...newPost, id: data.id || Date.now() };
129+
130+
// Yeni gönderiyi manuel olarak listeye ekle (renderPosts fonksiyonunu burası için özelleştirebiliriz veya manuel HTML oluşturabiliriz)
131+
const { id, title: pTitle, body: pBody } = fakePostWithId;
132+
133+
const postHTML = `
134+
<div class="post-card new-post" id="post-${id}">
135+
<div class="post-content">
136+
<h4>${pTitle}</h4>
137+
<p>${pBody}</p>
138+
</div>
139+
<div class="post-actions">
140+
<button class="btn primary btn-sm" data-action="edit" data-id="${id}">Düzenle</button>
141+
<button class="btn danger btn-sm" data-action="delete" data-id="${id}">Sil</button>
142+
</div>
143+
</div>`;
144+
145+
postList.insertAdjacentHTML("afterbegin", postHTML);
146+
147+
notify("success", "Yeni gönderi eklendi!");
148+
e.target.reset();
149+
} catch (err) {
150+
console.error(err);
151+
notify("error", "Gönderi eklenirken hata oluştu.");
152+
}
153+
});
154+
155+
// --- Olay Delegasyonu (Event Delegation) ---
156+
// Buttonlara tek tek olay dinleyicisi eklemek yerine, ebeveyn (postList) elemana ekliyoruz.
157+
postList.addEventListener("click", async (e) => {
158+
const target = e.target;
159+
160+
// Tıklanan eleman bir buton mu kontrol et
161+
if (target.tagName !== "BUTTON") return;
162+
163+
// Dataset üzerinden aksiyon ve ID bilgisini al (Destructuring)
164+
const { action, id } = target.dataset;
165+
166+
if (action === "delete") {
167+
await deletePost(id);
168+
} else if (action === "edit") {
169+
await editPost(id);
170+
}
171+
});
172+
173+
// 3. Gönderi Sil
174+
async function deletePost(id) {
175+
if (!confirm("Bu gönderiyi silmek istediğine emin misin?")) return;
176+
177+
try {
178+
await axios.delete(`/posts/${id}`);
179+
180+
const postEl = document.getElementById(`post-${id}`);
181+
if (postEl) {
182+
postEl.remove();
183+
notify("success", "Gönderi silindi!");
184+
}
185+
} catch (err) {
186+
console.error(err);
187+
notify("error", "Silinirken hata oluştu.");
188+
}
189+
}
190+
191+
// 4. Gönderi Düzenle
192+
async function editPost(id) {
193+
const postEl = document.getElementById(`post-${id}`);
194+
const titleEl = postEl.querySelector("h4");
195+
const bodyEl = postEl.querySelector("p");
196+
197+
const newTitle = prompt("Yeni başlık:", titleEl.innerText);
198+
if (newTitle === null) return;
199+
200+
const newBody = prompt("Yeni içerik:", bodyEl.innerText);
201+
if (newBody === null) return;
202+
203+
try {
204+
await axios.patch(`/posts/${id}`, {
205+
title: newTitle,
206+
body: newBody
207+
});
208+
209+
titleEl.innerText = newTitle;
210+
bodyEl.innerText = newBody;
211+
212+
notify("success", "Gönderi güncellendi!");
213+
} catch (err) {
214+
console.error(err);
215+
notify("error", "Güncelleme başarısız.");
216+
}
217+
}
218+
219+
// --- Başlangıç ---
220+
loadPosts();

‎project/index.html‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>JS Mini Project - CRUD</title>
8+
<!-- Google Fonts -->
9+
<link rel="preconnect" href="https://fonts.googleapis.com">
10+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12+
<!-- iziToast CSS -->
13+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/css/iziToast.min.css"
14+
integrity="sha512-O03ntXoVqaGUTAeAmvQ2YSzkCvclZEcPQu1eqloPaHfJ5RuNGiS4l+3duaidD801P50J28EHyonCV06CUlTSag=="
15+
crossorigin="anonymous" referrerpolicy="no-referrer" />
16+
<!-- Custom CSS -->
17+
<link rel="stylesheet" href="style.css" />
18+
</head>
19+
20+
<body>
21+
<div class="container">
22+
<header>
23+
<h1>Yönetim Paneli</h1>
24+
<p>Kullanıcıları ve Gönderileri Yönet</p>
25+
</header>
26+
27+
<section class="section users-section">
28+
<div class="section-header">
29+
<h2>Kullanıcılar</h2>
30+
<button id="load-users" class="btn primary">Kullanıcıları Getir</button>
31+
</div>
32+
<div id="user-list" class="grid-list"></div>
33+
</section>
34+
35+
<hr />
36+
37+
<section class="section posts-section">
38+
<div class="section-header">
39+
<h2>Gönderiler</h2>
40+
<div class="actions">
41+
<!-- Initial posts loaded by script logic usually, but keep button if manual trigger wanted -->
42+
<!-- <button id="load-posts" class="btn secondary">Reset Posts</button> -->
43+
</div>
44+
</div>
45+
46+
<div class="create-post-container">
47+
<h3>Yeni Gönderi Oluştur</h3>
48+
<form id="post-form">
49+
<div class="form-group">
50+
<input type="text" name="title" placeholder="Başlık" required />
51+
</div>
52+
<div class="form-group">
53+
<textarea name="body" placeholder="İçerik..." rows="3" required></textarea>
54+
</div>
55+
<button type="submit" class="btn success">Paylaş</button>
56+
</form>
57+
</div>
58+
59+
<div id="post-list" class="card-list">
60+
<!-- Posts will be injected here -->
61+
</div>
62+
63+
<div class="load-more-container">
64+
<button id="load-more" class="btn outline">Daha Fazla Yükle</button>
65+
</div>
66+
</section>
67+
</div>
68+
69+
<!-- iziToast JS -->
70+
<script src="https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/js/iziToast.min.js"
71+
integrity="sha512-Zq9o+E00xhhR/7vJ49mxFNJ0KQw1E1TMWkPTxrWCNpf07qljZgjOA5+CYKl/F80yow1g9LiXQhWr6n/8dfkM5g=="
72+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
73+
<!-- App JS -->
74+
<script type="module" src="app.js"></script>
75+
</body>
76+
77+
</html>

0 commit comments

Comments
 (0)