ai ile yapılan büyük güncelleme

This commit is contained in:
2026-07-28 10:21:14 +03:00
parent 371eecde2f
commit e71b6d4a91
9 changed files with 781 additions and 314 deletions
+129 -23
View File
@@ -1,30 +1,136 @@
export function renderDesktop() {
const html = `
<!-- absolute top-8 right-8 ile sağ üste sabitliyoruz -->
<div class="absolute top-8 right-8 bg-slate-900/40 backdrop-blur-2xl border border-white/10 p-6 rounded-3xl shadow-2xl max-w-md w-full text-left space-y-3">
<div class="flex items-center gap-3">
<div class="inline-flex p-2 bg-indigo-500/20 border border-indigo-500/30 rounded-xl text-indigo-300 text-xl">
</div>
<h2 class="text-xl font-bold text-white">Masaüstü</h2>
</div>
<p class="text-slate-300 text-xs leading-relaxed">
Modüler mimariye geçtik! Arka planı değiştirmek için sol menüdeki <b>Ayarlar</b> sekmesine geçebilirsin.
</p>
</div>
import { gregorianToHijri } from "../utils/hijri.js";
import { fetchWeather, getWeatherIcon, getWeatherDesc } from "../utils/weather.js";
import { fetchPrayerTimes } from "../utils/prayerTimes.js";
<div class="absolute middle-8 right-8 bg-slate-900/40 backdrop-blur-2xl border border-white/10 p-6 rounded-3xl shadow-2xl max-w-md w-full text-left space-y-3">
<div class="flex items-center gap-3">
<div class="inline-flex p-1 bg-indigo-500/20 border border-indigo-500/30 rounded-xl text-indigo-300 text-xl">
🌙
export function renderDesktop(selectedCity = "İstanbul") {
const hijri = gregorianToHijri();
const today = new Date();
const dateStr = today.toLocaleDateString("tr-TR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
const html = `
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 w-full max-w-6xl">
<div class="bg-slate-900/40 backdrop-blur-2xl border border-white/10 p-6 rounded-3xl shadow-2xl text-left space-y-3">
<div class="flex items-center gap-3">
<div class="inline-flex p-2 bg-indigo-500/20 border border-indigo-500/30 rounded-xl text-indigo-300 text-xl">
</div>
<h2 class="text-xl font-bold text-white">Masaüstü</h2>
</div>
<h2 class="text-xl font-bold text-white">Takvim</h2>
<p class="text-slate-300 text-xs leading-relaxed">
Arka planı değiştirmek için sol menüdeki <b>Ayarlar</b> sekmesine geçebilirsin.
</p>
</div>
<div class="bg-slate-900/40 backdrop-blur-2xl border border-white/10 p-6 rounded-3xl shadow-2xl text-left space-y-3">
<div class="flex items-center gap-3">
<div class="inline-flex p-2 bg-emerald-500/20 border border-emerald-500/30 rounded-xl text-emerald-300 text-xl">
📅
</div>
<h2 class="text-xl font-bold text-white">Hicri Takvim</h2>
</div>
<p class="text-slate-300 text-xs leading-relaxed">
<span class="text-white font-semibold">${hijri.weekday}</span><br />
${hijri.day} ${hijri.monthName} ${hijri.year}<br />
<span class="text-slate-400">${dateStr}</span>
</p>
</div>
<div class="bg-slate-900/40 backdrop-blur-2xl border border-white/10 p-6 rounded-3xl shadow-2xl text-left space-y-3">
<div class="flex items-center gap-3">
<div class="inline-flex p-2 bg-sky-500/20 border border-sky-500/30 rounded-xl text-sky-300 text-xl">
🌤️
</div>
<h2 class="text-xl font-bold text-white">Hava Durumu</h2>
</div>
<p class="text-slate-300 text-xs leading-relaxed" id="weatherWidget">
Yükleniyor...
</p>
</div>
<div class="bg-slate-900/40 backdrop-blur-2xl border border-white/10 p-6 rounded-3xl shadow-2xl text-left space-y-3">
<div class="flex items-center gap-3">
<div class="inline-flex p-2 bg-orange-500/20 border border-orange-500/30 rounded-xl text-orange-300 text-xl">
🌅
</div>
<h2 class="text-xl font-bold text-white">İmsakiye</h2>
</div>
<p class="text-slate-300 text-xs leading-relaxed" id="imsakiyeWidget">
Yükleniyor...
</p>
</div>
<div class="bg-slate-900/40 backdrop-blur-2xl border border-white/10 p-6 rounded-3xl shadow-2xl text-left space-y-3 cursor-pointer hover:border-white/20 transition-all" id="kazaShortcut">
<div class="flex items-center gap-3">
<div class="inline-flex p-2 bg-amber-500/20 border border-amber-500/30 rounded-xl text-amber-300 text-xl">
🕌
</div>
<h2 class="text-xl font-bold text-white">Kaza Namaz</h2>
</div>
<p class="text-slate-300 text-xs leading-relaxed">
Kaza namaz takibine geçmek için tıkla.
</p>
</div>
<p class="text-slate-300 text-xs leading-relaxed">
Modüler mimariye geçtik! Arka planı değiştirmek için sol menüdeki <b>Ayarlar</b> sekmesine geçebilirsin.
</p>
</div>
`;
return { html, initEvents: () => {} };
return {
html,
initEvents: (container) => {
loadWeather(container, selectedCity);
loadImsakiye(container, selectedCity);
setupKazaShortcut(container);
},
};
}
async function loadWeather(container, selectedCity) {
const widget = container.querySelector("#weatherWidget");
if (!widget) return;
const data = await fetchWeather(selectedCity);
if (!data) {
widget.innerHTML = "Hava durumu alınamadı.";
return;
}
const icon = getWeatherIcon(data.weathercode);
const desc = getWeatherDesc(data.weathercode);
const temp = Math.round(data.temperature);
widget.innerHTML = `
<span class="text-2xl">${icon}</span> <span class="text-white font-semibold">${temp}°C</span>
<br />
${desc} <span class="text-slate-400">(${data.cityName})</span>
`;
}
async function loadImsakiye(container, selectedCity) {
const widget = container.querySelector("#imsakiyeWidget");
if (!widget) return;
const times = await fetchPrayerTimes(selectedCity);
if (!times) {
widget.innerHTML = "Namaz vakitleri alınamadı.";
return;
}
widget.innerHTML = `
🌅 İmsak: <span class="text-white font-semibold">${times.imsak}</span><br />
🕌 İftar: <span class="text-emerald-300 font-semibold">${times.maghrib}</span>
`;
}
function setupKazaShortcut(container) {
const shortcut = container.querySelector("#kazaShortcut");
if (!shortcut) return;
shortcut.addEventListener("click", () => {
const event = new CustomEvent("navigate", { detail: "kaza" });
window.dispatchEvent(event);
});
}
+245
View File
@@ -0,0 +1,245 @@
import { gregorianToHijri, calculateMissedPrayers, getHijriAge } from "../utils/hijri.js";
const PRAYERS = [
{ id: "fajr", name: "SABAH", arabic: "Fecr" },
{ id: "dhuhr", name: "ÖĞLE", arabic: "Zuhur" },
{ id: "asr", name: "İKİNDİ", arabic: "Asr" },
{ id: "maghrib", name: "AKŞAM", arabic: "Mağrib" },
{ id: "isha", name: "YATSI", arabic: "İşa" },
];
export function renderKaza(pb) {
const profileS = localStorage.getItem("kaza_profile");
let profile = profileS ? JSON.parse(profileS) : null;
const hijri = gregorianToHijri();
const hijriAge = profile ? getHijriAge(profile.birthDate) : 0;
const calc = profile ? calculateMissedPrayers(profile.birthDate, profile.gender) : { missed: 0, days: 0 };
const html = `
<div class="bg-slate-900/60 backdrop-blur-2xl border border-white/10 p-8 rounded-3xl shadow-2xl max-w-4xl w-full h-[80vh] flex flex-col">
<button id="closeBtn" class="absolute top-4 right-4 bg-rose-500/20 hover:bg-rose-500/40 text-rose-300 border border-rose-500/30 w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs transition-all">
</button>
<h2 class="text-2xl font-bold text-white mb-1">🕌 Kaza Namaz Takibi</h2>
<p class="text-slate-400 text-sm mb-6">
Hicri yaşını ve kalan kaza namazını takip et.
</p>
${!profile ? `
<div class="bg-slate-900/40 border border-white/10 p-6 rounded-2xl mb-6">
<h3 class="text-sm font-semibold text-slate-300 mb-4">Profil Bilgileri</h3>
<form id="profileForm" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-xs text-slate-400 mb-1">Doğum Tarihi</label>
<input type="date" id="birthDate" required class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white text-sm" />
</div>
<div>
<label class="block text-xs text-slate-400 mb-1">Cinsiyet</label>
<select id="gender" required class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white text-sm">
<option value="male">Erkek</option>
<option value="female">Kadın</option>
</select>
</div>
<div class="flex items-end">
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-xl text-sm font-medium transition-all">
Hesapla
</button>
</div>
</form>
</div>
` : `
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="bg-slate-900/40 border border-white/10 p-4 rounded-2xl text-center">
<p class="text-xs text-slate-400 mb-1">Hicri Yaş</p>
<p class="text-2xl font-bold text-white" id="hijriAgeDisplay">-</p>
</div>
<div class="bg-slate-900/40 border border-white/10 p-4 rounded-2xl text-center">
<p class="text-xs text-slate-400 mb-1">Toplam Kaza</p>
<p class="text-2xl font-bold text-rose-300" id="totalKazaDisplay">-</p>
</div>
<div class="bg-slate-900/40 border border-white/10 p-4 rounded-2xl text-center">
<p class="text-xs text-slate-400 mb-1">Kalan</p>
<p class="text-2xl font-bold text-emerald-300" id="remainingDisplay">-</p>
</div>
</div>
<div class="bg-slate-900/40 border border-white/10 p-4 rounded-2xl mb-6">
<h3 class="text-sm font-semibold text-slate-300 mb-3">Kaza Namaz Ekle</h3>
<form id="addKazaForm" class="flex items-center gap-3">
<select id="kazaPrayerType" class="bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white text-sm">
${PRAYERS.map((p) => `<option value="${p.id}">${p.name}</option>`).join("")}
</select>
<input type="number" id="kazaCount" min="1" value="1" required class="w-20 bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white text-sm" />
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-xl text-sm font-medium transition-all">
Ekle
</button>
</form>
<p id="kazaMsg" class="text-xs mt-2 hidden"></p>
</div>
<div class="flex-1 overflow-y-auto pr-2">
<h3 class="text-sm font-semibold text-slate-300 mb-4">Bugünkü Namazlar</h3>
<div class="grid grid-cols-2 md:grid-cols-5 gap-3 mb-6">
${PRAYERS.map((p) => {
const todayKey = new Date().toISOString().split("T")[0];
const alreadyDone = profile.todayLogs?.some(
(l) => l.prayerId === p.id && l.date === todayKey
);
return `
<div class="bg-slate-900/40 border ${alreadyDone ? "border-emerald-500/50 bg-emerald-500/10" : "border-white/10"} rounded-2xl p-4 text-center transition-all">
<p class="text-xs text-slate-400">${p.arabic}</p>
<p class="text-sm font-semibold text-white">${p.name}</p>
${alreadyDone ? "<p class='text-xs text-emerald-400 mt-1'>✓ Kılındı</p>" : ""}
</div>
`;
}).join("")}
</div>
<h3 class="text-sm font-semibold text-slate-300 mb-4">Son İşlemler</h3>
<div class="space-y-2" id="logsContainer">
<p class="text-xs text-slate-500">Yükleniyor...</p>
</div>
</div>
`}
</div>
`;
return {
html,
initEvents: (container) => {
const closeBtn = container.querySelector("#closeBtn");
if (closeBtn) {
closeBtn.addEventListener("click", () => {
const event = new CustomEvent("navigate", { detail: "desktop" });
window.dispatchEvent(event);
});
}
if (!profile) {
const form = container.querySelector("#profileForm");
if (form) {
form.addEventListener("submit", (e) => {
e.preventDefault();
const birthDate = container.querySelector("#birthDate").value;
const gender = container.querySelector("#gender").value;
if (!birthDate) {
alert("Lütfen doğum tarihinizi girin.");
return;
}
profile = {
birthDate,
gender,
logs: [],
todayLogs: [],
createdAt: new Date().toISOString(),
};
localStorage.setItem("kaza_profile", JSON.stringify(profile));
const event = new CustomEvent("navigate", { detail: "kaza" });
window.dispatchEvent(event);
});
}
} else {
refreshKazaStats(container, pb, profile);
const addForm = container.querySelector("#addKazaForm");
if (addForm) {
addForm.addEventListener("submit", async (e) => {
e.preventDefault();
const prayerId = container.querySelector("#kazaPrayerType").value;
const countInput = container.querySelector("#kazaCount");
const count = Math.max(1, parseInt(countInput?.value || "1", 10));
const msg = container.querySelector("#kazaMsg");
try {
await pb.collection("kaza_logs").create({
user: pb.authStore.model.id,
prayerId,
count,
date: new Date().toISOString().split("T")[0],
});
if (msg) {
msg.textContent = `${count} kaza namaz eklendi!`;
msg.className = "text-xs mt-2 text-emerald-400";
msg.classList.remove("hidden");
}
if (countInput) countInput.value = "1";
refreshKazaStats(container, pb, profile);
} catch (err) {
console.error("Kaza eklenemedi:", err);
if (msg) {
msg.textContent = "Eklenemedi. PocketBase bağlantısını kontrol et.";
msg.className = "text-xs mt-2 text-rose-400";
msg.classList.remove("hidden");
}
}
});
}
}
},
};
}
async function refreshKazaStats(container, pb, profile) {
if (!profile) return;
const hijriAge = getHijriAge(profile.birthDate);
const calc = calculateMissedPrayers(profile.birthDate, profile.gender);
const ageEl = container.querySelector("#hijriAgeDisplay");
const totalEl = container.querySelector("#totalKazaDisplay");
const remainEl = container.querySelector("#remainingDisplay");
const logsContainer = container.querySelector("#logsContainer");
if (ageEl) ageEl.textContent = hijriAge;
if (totalEl) totalEl.textContent = calc.missed;
let totalCompleted = 0;
let logs = [];
try {
const records = await pb.collection("kaza_logs").getFullList({
filter: `user = "${pb.authStore.model.id}"`,
sort: "-createdAt",
});
logs = records.map((r) => ({
id: r.id,
prayerId: r.prayerId,
count: r.count || 1,
date: r.date,
createdAt: r.createdAt,
}));
totalCompleted = logs.reduce((sum, l) => sum + (l.count || 1), 0);
} catch (err) {
console.error("Loglar yüklenemedi:", err);
}
if (remainEl) remainEl.textContent = Math.max(0, calc.missed - totalCompleted);
const todayKey = new Date().toISOString().split("T")[0];
const todayLogs = logs.filter((l) => l.date === todayKey);
profile.todayLogs = todayLogs;
if (logsContainer) {
logsContainer.innerHTML = logs
.slice(0, 20)
.map((log) => {
const prayer = PRAYERS.find((p) => p.id === log.prayerId);
return `
<div class="flex items-center justify-between bg-slate-900/20 border border-white/5 rounded-xl px-4 py-2">
<span class="text-xs text-slate-300">${prayer?.name || log.prayerId} x${log.count} - ${log.date}</span>
<span class="text-xs text-emerald-400">+${log.count}</span>
</div>
`;
})
.join("") || '<p class="text-xs text-slate-500">Henüz kayıt yok.</p>';
}
}
+121 -21
View File
@@ -1,4 +1,6 @@
export async function renderSettings(pb, currentBg, onBgSelect) {
import { TURKISH_CITIES } from "../utils/locations.js";
export async function renderSettings(pb, currentBg, onBgSelect, currentCity, onCitySelect) {
let bgList = [];
try {
@@ -12,6 +14,11 @@ export async function renderSettings(pb, currentBg, onBgSelect) {
console.error("Görseller yüklenemedi:", err);
}
const cityOptions = TURKISH_CITIES.map((c) => {
const selected = c.name === currentCity ? "selected" : "";
return `<option value="${c.name}" ${selected}>${c.name}</option>`;
}).join("");
const html = `
<div class="bg-slate-900/60 backdrop-blur-2xl border border-white/10 p-8 rounded-3xl shadow-2xl max-w-4xl w-full h-[80vh] flex flex-col">
<button id="closeBtn" class="absolute top-4 right-4 bg-rose-500/20 hover:bg-rose-500/40 text-rose-300 border border-rose-500/30 w-8 h-8 rounded-full flex items-center justify-center font-bold text-xs transition-all">
@@ -20,16 +27,41 @@ export async function renderSettings(pb, currentBg, onBgSelect) {
<h2 class="text-2xl font-bold text-white mb-2">⚙️ Sistem Ayarları</h2>
<p class="text-slate-400 text-sm mb-6">Seçtiğin duvar kağıdı doğrudan PocketBase hesabına kaydedilir.</p>
<div class="mb-6 p-4 rounded-2xl border border-white/10 bg-slate-900/40">
<h3 class="text-sm font-semibold text-slate-300 mb-3">Bölge Ayarı</h3>
<div class="flex items-center gap-3">
<select id="citySelect" class="bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white text-sm flex-1">
${cityOptions}
</select>
<button id="saveCityBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-xl text-xs font-medium transition-all">
Kaydet
</button>
</div>
<p id="cityMsg" class="text-xs mt-2 hidden"></p>
</div>
<div class="mb-6 p-4 rounded-2xl border border-white/10 bg-slate-900/40">
<h3 class="text-sm font-semibold text-slate-300 mb-3">Yeni Arka Plan Ekle</h3>
<form id="uploadForm" class="flex items-center gap-3">
<input type="file" id="bgFile" accept="image/*" required class="text-xs text-slate-300" />
<input type="text" id="bgTitle" placeholder="Başlık (opsiyonel)" class="bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-white text-xs flex-1" />
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-xl text-xs font-medium transition-all">
Yükle
</button>
</form>
<p id="uploadMsg" class="text-xs mt-2 hidden"></p>
</div>
<div class="flex-1 overflow-y-auto pr-2">
<h3 class="text-sm font-semibold text-slate-300 mb-4">Arka Plan Galerisi (${bgList.length})</h3>
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
${bgList
.map(
(bg) => `
<div data-url="${bg.url}" class="bg-card group relative h-40 rounded-2xl overflow-hidden border-2 cursor-pointer transition-all duration-300 hover:scale-[1.02] ${currentBg === bg.url ? "border-indigo-500 ring-4 ring-indigo-500/20" : "border-white/10 hover:border-white/30"}">
<div data-url="${bg.url}" class="group relative h-40 rounded-2xl overflow-hidden border-2 cursor-pointer transition-all duration-300 hover:scale-[1.02] ${currentBg === bg.url ? "border-indigo-500 ring-4 ring-indigo-500/20" : "border-white/10 hover:border-white/30"}">
<img src="${bg.url}" alt="${bg.title}" class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-linear-to-t from-slate-950/80 via-transparent to-transparent opacity-80 group-hover:opacity-100 transition-opacity flex items-end p-3">
<div class="absolute inset-0 bg-gradient-to-t from-slate-950/80 via-transparent to-transparent opacity-80 group-hover:opacity-100 transition-opacity flex items-end p-3">
<span class="text-xs font-medium text-white truncate">${bg.title}</span>
</div>
</div>
@@ -44,23 +76,91 @@ export async function renderSettings(pb, currentBg, onBgSelect) {
return {
html,
initEvents: (container) => {
container.querySelectorAll(".bg-card").forEach((card) => {
card.addEventListener("click", async () => {
const selectedUrl = card.getAttribute("data-url");
try {
// PocketBase'deki aktif kullanıcının 'selected_bg' alanını güncelliyoruz
await pb.collection("users").update(pb.authStore.model.id, {
selected_bg: selectedUrl,
});
// Ekranı yeni ayarla güncelle
onBgSelect(selectedUrl);
} catch (err) {
console.error("Ayar kaydedilemedi:", err);
}
});
});
setupCity(container, pb, onCitySelect);
setupUpload(container, pb, onBgSelect);
setupGallery(container, pb, onBgSelect);
},
};
}
function setupCity(container, pb, onCitySelect) {
const saveBtn = container.querySelector("#saveCityBtn");
const select = container.querySelector("#citySelect");
const msg = container.querySelector("#cityMsg");
if (!saveBtn || !select) return;
saveBtn.addEventListener("click", async () => {
const city = select.value;
try {
await pb.collection("users").update(pb.authStore.model.id, {
selected_city: city,
});
if (onCitySelect) onCitySelect(city);
showMsg(msg, "Bölge kaydedildi!", true);
} catch (err) {
console.error("Bölge kaydedilemedi:", err);
showMsg(msg, "Kaydedilemedi. Varsayılan olarak kullanılacak.", false);
if (onCitySelect) onCitySelect(city);
}
});
}
function setupUpload(container, pb, onBgSelect) {
const form = container.querySelector("#uploadForm");
const msg = container.querySelector("#uploadMsg");
if (!form) return;
form.addEventListener("submit", async (e) => {
e.preventDefault();
const fileInput = container.querySelector("#bgFile");
const titleInput = container.querySelector("#bgTitle");
const file = fileInput?.files?.[0];
if (!file) {
showMsg(msg, "Lütfen bir resim seçin.", false);
return;
}
try {
await pb.collection("backgrounds").create({
title: titleInput?.value || "Duvar Kağıdı",
image: file,
});
showMsg(msg, "Arka plan yüklendi!", true);
fileInput.value = "";
if (titleInput) titleInput.value = "";
setTimeout(() => {
window.location.reload();
}, 800);
} catch (err) {
console.error("Yükleme hatası:", err);
showMsg(msg, "Yükleme başarısız. PocketBase bağlantısını kontrol et.", false);
}
});
}
function setupGallery(container, pb, onBgSelect) {
container.querySelectorAll("[data-url]").forEach((card) => {
card.addEventListener("click", async () => {
const selectedUrl = card.getAttribute("data-url");
try {
await pb.collection("users").update(pb.authStore.model.id, {
selected_bg: selectedUrl,
});
onBgSelect(selectedUrl);
} catch (err) {
console.error("Ayar kaydedilemedi:", err);
}
});
});
}
function showMsg(el, text, success) {
el.textContent = text;
el.className = `text-xs mt-2 ${success ? "text-emerald-400" : "text-rose-400"}`;
el.classList.remove("hidden");
}