diff --git a/package-lock.json b/package-lock.json index 4228a29..431325d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1457,256 +1457,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "optional": true, - "peer": true, - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/src/main.js b/src/main.js index b685a6e..cc4ae62 100755 --- a/src/main.js +++ b/src/main.js @@ -2,11 +2,15 @@ import "./style.css"; import PocketBase from "pocketbase"; import { renderSettings } from "./pages/settings.js"; import { renderDesktop } from "./pages/desktop.js"; +import { renderKaza } from "./pages/kaza.js"; +import { getDefaultCity, getCityByName } from "./utils/locations.js"; const pb = new PocketBase("https://pocketbase.iontalp.com"); const app = document.getElementById("app"); let activeTab = "desktop"; +let sidebarOpen = window.innerWidth >= 768; +let selectedCity = localStorage.getItem("selected_city") || "İstanbul"; async function render() { const user = pb.authStore.model; @@ -16,7 +20,6 @@ async function render() { return; } - // PocketBase tam avatar URL formatı const avatarUrl = user?.avatar ? `https://pocketbase.iontalp.com/api/files/_pb_users_auth_/${user.id}/${user.avatar}?thumb=100x100` : null; @@ -25,44 +28,73 @@ async function render() { user.selected_bg || "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=1920"; - // Sayfayı Yükle + if (user.selected_city) { + selectedCity = user.selected_city; + localStorage.setItem("selected_city", selectedCity); + } else { + const stored = localStorage.getItem("selected_city"); + if (stored) { + selectedCity = stored; + } else { + selectedCity = getDefaultCity().name; + } + } + let currentPage; if (activeTab === "settings") { currentPage = await renderSettings(pb, currentBg, (newBg) => { pb.authStore.model.selected_bg = newBg; render(); + }, selectedCity, (city) => { + selectedCity = city; + localStorage.setItem("selected_city", city); + render(); }); + } else if (activeTab === "kaza") { + currentPage = renderKaza(pb); } else { - currentPage = renderDesktop(); + currentPage = renderDesktop(selectedCity); } app.innerHTML = `
-
- -
`; - // Etkinlikleri bağla const mainContent = document.getElementById("mainContent"); currentPage.initEvents(mainContent); @@ -89,6 +119,10 @@ async function render() { activeTab = "desktop"; render(); }); + document.getElementById("navKaza").addEventListener("click", () => { + activeTab = "kaza"; + render(); + }); document.getElementById("navSettings").addEventListener("click", () => { activeTab = "settings"; render(); @@ -98,12 +132,36 @@ async function render() { render(); }); - document.getElementById("closeBtn").addEventListener("click", () => { - activeTab = "desktop"; - render(); - }); + const globalToggle = document.getElementById("globalSidebarToggle"); + if (globalToggle) { + globalToggle.addEventListener("click", () => { + sidebarOpen = !sidebarOpen; + render(); + }); + } + + const overlay = document.getElementById("sidebarOverlay"); + if (overlay) { + overlay.addEventListener("click", () => { + sidebarOpen = false; + render(); + }); + } + + const closeBtn = document.getElementById("closeBtn"); + if (closeBtn) { + closeBtn.addEventListener("click", () => { + activeTab = "desktop"; + render(); + }); + } } +window.addEventListener("navigate", (e) => { + activeTab = e.detail; + render(); +}); + function renderLogin() { app.innerHTML = `
diff --git a/src/pages/desktop.js b/src/pages/desktop.js index 94ab855..294fa1d 100644 --- a/src/pages/desktop.js +++ b/src/pages/desktop.js @@ -1,30 +1,136 @@ -export function renderDesktop() { - const html = ` - -
-
-
- ✨ -
-

Masaüstü

-
-

- Modüler mimariye geçtik! Arka planı değiştirmek için sol menüdeki Ayarlar sekmesine geçebilirsin. -

-
+import { gregorianToHijri } from "../utils/hijri.js"; +import { fetchWeather, getWeatherIcon, getWeatherDesc } from "../utils/weather.js"; +import { fetchPrayerTimes } from "../utils/prayerTimes.js"; -
-
-
- 🌙 +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 = ` +
+
+
+
+ ✨ +
+

Masaüstü

-

Takvim

+

+ Arka planı değiştirmek için sol menüdeki Ayarlar sekmesine geçebilirsin. +

+
+ +
+
+
+ 📅 +
+

Hicri Takvim

+
+

+ ${hijri.weekday}
+ ${hijri.day} ${hijri.monthName} ${hijri.year}
+ ${dateStr} +

+
+ +
+
+
+ 🌤️ +
+

Hava Durumu

+
+

+ Yükleniyor... +

+
+ +
+
+
+ 🌅 +
+

İmsakiye

+
+

+ Yükleniyor... +

+
+ +
+
+
+ 🕌 +
+

Kaza Namaz

+
+

+ Kaza namaz takibine geçmek için tıkla. +

-

- Modüler mimariye geçtik! Arka planı değiştirmek için sol menüdeki Ayarlar sekmesine geçebilirsin. -

`; - 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 = ` + ${icon} ${temp}°C +
+ ${desc} (${data.cityName}) + `; +} + +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: ${times.imsak}
+ 🕌 İftar: ${times.maghrib} + `; +} + +function setupKazaShortcut(container) { + const shortcut = container.querySelector("#kazaShortcut"); + if (!shortcut) return; + + shortcut.addEventListener("click", () => { + const event = new CustomEvent("navigate", { detail: "kaza" }); + window.dispatchEvent(event); + }); } diff --git a/src/pages/kaza.js b/src/pages/kaza.js new file mode 100644 index 0000000..f89470d --- /dev/null +++ b/src/pages/kaza.js @@ -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 = ` +
+ + +

🕌 Kaza Namaz Takibi

+

+ Hicri yaşını ve kalan kaza namazını takip et. +

+ + ${!profile ? ` +
+

Profil Bilgileri

+
+
+ + +
+
+ + +
+
+ +
+
+
+ ` : ` +
+
+

Hicri Yaş

+

-

+
+
+

Toplam Kaza

+

-

+
+
+

Kalan

+

-

+
+
+ +
+

Kaza Namaz Ekle

+
+ + + +
+ +
+ +
+

Bugünkü Namazlar

+
+ ${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 ` +
+

${p.arabic}

+

${p.name}

+ ${alreadyDone ? "

✓ Kılındı

" : ""} +
+ `; + }).join("")} +
+ +

Son İşlemler

+
+

Yükleniyor...

+
+
+ `} +
+ `; + + 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 ` +
+ ${prayer?.name || log.prayerId} x${log.count} - ${log.date} + +${log.count} +
+ `; + }) + .join("") || '

Henüz kayıt yok.

'; + } +} diff --git a/src/pages/settings.js b/src/pages/settings.js index 728c117..f85cec9 100644 --- a/src/pages/settings.js +++ b/src/pages/settings.js @@ -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 ``; + }).join(""); + const html = `
+
+ +
+ +
+

Yeni Arka Plan Ekle

+
+ + + +
+ +
+

Arka Plan Galerisi (${bgList.length})

- +
${bgList .map( (bg) => ` -
+
${bg.title} -
+
${bg.title}
@@ -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"); +} diff --git a/src/utils/hijri.js b/src/utils/hijri.js new file mode 100644 index 0000000..c73e5fe --- /dev/null +++ b/src/utils/hijri.js @@ -0,0 +1,96 @@ +const HIJRI_MONTHS = [ + "Muharrem", "Safer", "Rebiülevvel", "Rebiülahir", + "Cemaziyelevvel", "Cemaziyelahir", "Receb", "Şaban", + "Ramazan", "Şevval", "Zilkade", "Zilhicce", +]; + +const DAYS = [ + "Pazar", "Pazartesi", "Salı", "Çarşamba", + "Perşembe", "Cuma", "Cumartesi", +]; + +export function gregorianToHijri(date = new Date()) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + + let a = Math.floor((14 - month) / 12); + let y = year + 4800 - a; + let m = month + 12 * a - 3; + + let jdn = + day + + Math.floor((153 * m + 2) / 5) + + 365 * y + + Math.floor(y / 4) - + Math.floor(y / 100) + + Math.floor(y / 400) - + 32045; + + let l = jdn - 1948440 + 10632; + let n = Math.floor((l - 1) / 10631); + l = l - 10631 * n + 354; + let j = + Math.floor((10985 - l) / 5316) * Math.floor((50 * l) / 17719) + + Math.floor(l / 5670) * Math.floor((43 * l) / 15238); + l = + l - + Math.floor((30 - j) / 15) * Math.floor((17719 * j) / 50) - + Math.floor(j / 16) * Math.floor((15238 * j) / 43) + + 29; + let hijriMonth = Math.floor((24 * l) / 709); + let hijriDay = l - Math.floor((709 * hijriMonth) / 24); + let hijriYear = 30 * n + j - 30; + + if (hijriMonth > 12) { + hijriMonth -= 12; + hijriYear += 1; + } + + const weekday = DAYS[date.getDay()]; + + return { + year: hijriYear, + month: hijriMonth, + day: hijriDay, + monthName: HIJRI_MONTHS[hijriMonth - 1], + weekday, + }; +} + +export function calculateMissedPrayers(birthDate, gender, mukallafAge = 15) { + const birth = new Date(birthDate); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const mukallafDate = new Date(birth); + mukallafDate.setFullYear(mukallafDate.getFullYear() + mukallafAge); + + if (today <= mukallafDate) { + return { missed: 0, days: 0 }; + } + + const diffTime = today - mukallafDate; + const days = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + const missed = days * 5; + + return { missed, days, mukallafDate }; +} + +export function getHijriAge(birthDate, mukallafAge = 15) { + const birth = new Date(birthDate); + const today = new Date(); + + const hijriBirth = gregorianToHijri(birth); + const hijriToday = gregorianToHijri(today); + + let age = hijriToday.year - hijriBirth.year; + if ( + hijriToday.month < hijriBirth.month || + (hijriToday.month === hijriBirth.month && hijriToday.day < hijriBirth.day) + ) { + age -= 1; + } + + return Math.max(0, age); +} diff --git a/src/utils/locations.js b/src/utils/locations.js new file mode 100644 index 0000000..644f375 --- /dev/null +++ b/src/utils/locations.js @@ -0,0 +1,40 @@ +export const TURKISH_CITIES = [ + { name: "İstanbul", lat: 41.0082, lon: 28.9784 }, + { name: "Ankara", lat: 39.9334, lon: 32.8597 }, + { name: "İzmir", lat: 38.4237, lon: 27.1428 }, + { name: "Bursa", lat: 40.1885, lon: 29.0610 }, + { name: "Antalya", lat: 36.8969, lon: 30.7133 }, + { name: "Adana", lat: 37.0000, lon: 35.3213 }, + { name: "Konya", lat: 37.8713, lon: 32.4846 }, + { name: "Gaziantep", lat: 37.0662, lon: 37.3833 }, + { name: "Şanlıurfa", lat: 37.1674, lon: 38.7955 }, + { name: "Kocaeli", lat: 40.8533, lon: 29.8815 }, + { name: "Mersin", lat: 36.8121, lon: 34.6415 }, + { name: "Diyarbakır", lat: 37.9144, lon: 40.2306 }, + { name: "Hatay", lat: 36.4018, lon: 36.3498 }, + { name: "Manisa", lat: 38.6191, lon: 27.4289 }, + { name: "Kayseri", lat: 38.7312, lon: 35.4787 }, + { name: "Samsun", lat: 41.2867, lon: 36.3300 }, + { name: "Trabzon", lat: 41.0027, lon: 39.7168 }, + { name: "Erzurum", lat: 39.9000, lon: 41.2700 }, + { name: "Malatya", lat: 38.3552, lon: 38.3095 }, + { name: "Van", lat: 38.4891, lon: 43.4089 }, + { name: "Aydın", lat: 37.8444, lon: 27.8458 }, + { name: "Muğla", lat: 37.2153, lon: 28.3636 }, + { name: "Balıkesir", lat: 39.6484, lon: 27.8826 }, + { name: "Tekirdağ", lat: 41.0082, lon: 27.7144 }, + { name: "Sakarya", lat: 40.7569, lon: 30.3786 }, + { name: "Eskişehir", lat: 39.7767, lon: 30.5206 }, + { name: "Afyon", lat: 38.7569, lon: 30.5433 }, + { name: "Isparta", lat: 37.7648, lon: 30.5566 }, + { name: "Bolu", lat: 40.7356, lon: 31.6061 }, + { name: "Zonguldak", lat: 41.4564, lon: 31.7987 }, +]; + +export function getCityByName(name) { + return TURKISH_CITIES.find((c) => c.name === name) || TURKISH_CITIES[0]; +} + +export function getDefaultCity() { + return TURKISH_CITIES[0]; +} diff --git a/src/utils/prayerTimes.js b/src/utils/prayerTimes.js new file mode 100644 index 0000000..ac69ff3 --- /dev/null +++ b/src/utils/prayerTimes.js @@ -0,0 +1,26 @@ +export async function fetchPrayerTimes(cityName = "Istanbul") { + try { + const url = + "https://api.aladhan.com/v1/timingsByCity?city=" + + encodeURIComponent(cityName) + + "&country=Turkey&method=2"; + const res = await fetch(url); + if (!res.ok) { + throw new Error("Namaz vakitleri alınamadı"); + } + const data = await res.json(); + const t = data.data.timings; + return { + fajr: t.Fajr, + sunrise: t.Sunrise, + dhuhr: t.Dhuhr, + asr: t.Asr, + maghrib: t.Maghrib, + isha: t.Isha, + imsak: t.Imsak, + }; + } catch (err) { + console.error("Namaz vakti hatası:", err); + return null; + } +} diff --git a/src/utils/weather.js b/src/utils/weather.js new file mode 100644 index 0000000..26af395 --- /dev/null +++ b/src/utils/weather.js @@ -0,0 +1,46 @@ +import { getCityByName } from "./locations.js"; + +export async function fetchWeather(cityName = "İstanbul") { + try { + const city = getCityByName(cityName); + const url = + "https://api.open-meteo.com/v1/forecast?latitude=" + + city.lat + + "&longitude=" + + city.lon + + "¤t_weather=true"; + const res = await fetch(url); + if (!res.ok) { + throw new Error("Hava durumu alınamadı"); + } + const data = await res.json(); + return { ...data.current_weather, cityName }; + } catch (err) { + console.error("Hava durumu hatası:", err); + return null; + } +} + +export function getWeatherIcon(code) { + if (code === 0) return "☀️"; + if (code >= 1 && code <= 3) return "⛅"; + if (code >= 45 && code <= 48) return "🌫️"; + if (code >= 51 && code <= 55) return "🌧️"; + if (code >= 61 && code <= 65) return "🌧️"; + if (code >= 71 && code <= 77) return "❄️"; + if (code >= 80 && code <= 82) return "🌦️"; + if (code >= 95) return "⛈️"; + return "🌡️"; +} + +export function getWeatherDesc(code) { + if (code === 0) return "Açık"; + if (code >= 1 && code <= 3) return "Parçalı Bulutlu"; + if (code >= 45 && code <= 48) return "Sisli"; + if (code >= 51 && code <= 55) return "Çiseli"; + if (code >= 61 && code <= 65) return "Yağmurlu"; + if (code >= 71 && code <= 77) return "Karlı"; + if (code >= 80 && code <= 82) return "Sağanak Yağışlı"; + if (code >= 95) return "Fırtınalı"; + return "Hava Durumu"; +}