ai ile yapılan büyük güncelleme
This commit is contained in:
Generated
-250
@@ -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",
|
||||
|
||||
+78
-20
@@ -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 = `
|
||||
<div class="relative w-full h-screen overflow-hidden bg-slate-950 text-slate-100 flex">
|
||||
<!-- Arka Plan Görseli -->
|
||||
<div class="absolute inset-0 z-0 bg-center bg-no-repeat transition-all duration-700"
|
||||
style="background-image: url('${currentBg}'); background-size: 1920px 1080px;">
|
||||
</div>
|
||||
<div class="absolute inset-0 z-10 bg-slate-950/50 backdrop-blur-[2px]"></div>
|
||||
|
||||
<!-- Sol Menü (Sidebar) -->
|
||||
<aside class="relative z-30 w-72 h-screen bg-slate-900/80 backdrop-blur-xl border-r border-white/10 p-6 flex flex-col justify-between">
|
||||
<button id="globalSidebarToggle" class="fixed top-4 left-4 z-50 bg-white/10 hover:bg-white/20 text-white p-3 rounded-xl transition-all">
|
||||
${sidebarOpen ? "✕" : "☰"}
|
||||
</button>
|
||||
|
||||
${sidebarOpen ? `<div id="sidebarOverlay" class="fixed inset-0 bg-black/50 z-30 md:hidden"></div>` : ""}
|
||||
|
||||
<aside id="sidebar" class="fixed md:relative z-40 md:z-30 inset-y-0 left-0 ${sidebarOpen ? "w-72 translate-x-0" : "w-72 -translate-x-full md:w-20 md:translate-x-0"} h-screen bg-slate-900/80 backdrop-blur-xl border-r border-white/10 p-6 flex flex-col justify-between transition-all duration-300 overflow-hidden">
|
||||
<div>
|
||||
<h1 class="font-bold text-white mb-6 text-xl">Dashboard OS</h1>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="font-bold text-white text-xl ${sidebarOpen ? "block" : "hidden md:block"}">Dashboard OS</h1>
|
||||
</div>
|
||||
<nav class="space-y-2">
|
||||
<button id="navDesktop" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl font-medium ${activeTab === "desktop" ? "bg-white/10 text-white" : "text-slate-400 hover:text-white"}">🏠 Masaüstü</button>
|
||||
<button id="navSettings" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl font-medium ${activeTab === "settings" ? "bg-white/10 text-white" : "text-slate-400 hover:text-white"}">⚙️ Ayarlar</button>
|
||||
<button id="navDesktop" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl font-medium ${activeTab === "desktop" ? "bg-white/10 text-white" : "text-slate-400 hover:text-white"}">
|
||||
<span>🏠</span> <span class="${sidebarOpen ? "block" : "hidden md:block"}">Masaüstü</span>
|
||||
</button>
|
||||
<button id="navKaza" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl font-medium ${activeTab === "kaza" ? "bg-white/10 text-white" : "text-slate-400 hover:text-white"}">
|
||||
<span>🕌</span> <span class="${sidebarOpen ? "block" : "hidden md:block"}">Kaza Namaz</span>
|
||||
</button>
|
||||
<button id="navSettings" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl font-medium ${activeTab === "settings" ? "bg-white/10 text-white" : "text-slate-400 hover:text-white"}">
|
||||
<span>⚙️</span> <span class="${sidebarOpen ? "block" : "hidden md:block"}">Ayarlar</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Profil / Çıkış Alanı -->
|
||||
<div class="space-y-3 pt-4 border-t border-white/10">
|
||||
<div id="profileBtn" class="flex items-center gap-3 p-2 rounded-xl hover:bg-white/5 cursor-pointer transition-all">
|
||||
${
|
||||
avatarUrl
|
||||
? `<img src="${avatarUrl}" alt="Avatar" class="w-10 h-10 rounded-full object-cover border border-white/20 shadow-md" />`
|
||||
: `<div class="w-10 h-10 rounded-full bg-indigo-600/30 border border-indigo-500/30 flex items-center justify-center font-bold text-indigo-400">${(user.username || user.email).charAt(0).toUpperCase()}</div>`
|
||||
? `<img src="${avatarUrl}" alt="Avatar" class="w-10 h-10 rounded-full object-cover border border-white/20 shadow-md flex-shrink-0" />`
|
||||
: `<div class="w-10 h-10 rounded-full bg-indigo-600/30 border border-indigo-500/30 flex items-center justify-center font-bold text-indigo-400 flex-shrink-0">${(user.username || user.email).charAt(0).toUpperCase()}</div>`
|
||||
}
|
||||
<div class="overflow-hidden">
|
||||
<div class="overflow-hidden ${sidebarOpen ? "block" : "hidden md:block"}">
|
||||
<p class="text-sm font-medium text-white truncate">${user.name || user.email}</p>
|
||||
<p class="text-xs text-slate-400">Çevrimiçi</p>
|
||||
</div>
|
||||
@@ -74,14 +106,12 @@ async function render() {
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Ana İçerik -->
|
||||
<main id="mainContent" class="relative z-20 flex-1 h-screen p-8 flex items-center justify-center">
|
||||
<main id="mainContent" class="relative z-20 flex-1 h-screen p-8 flex items-center justify-center overflow-hidden">
|
||||
${currentPage.html}
|
||||
</main>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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 = `
|
||||
<div class="min-h-screen bg-slate-950 text-slate-100 flex items-center justify-center p-4">
|
||||
|
||||
+129
-23
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
+120
-20
@@ -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,6 +27,31 @@ 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>
|
||||
|
||||
@@ -27,9 +59,9 @@ export async function renderSettings(pb, currentBg, onBgSelect) {
|
||||
${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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user