From fd78b6e7d922bcb26d802dc0118bc672cff339c1 Mon Sep 17 00:00:00 2001 From: ht Date: Sun, 30 Aug 2026 00:24:33 +0300 Subject: [PATCH] =?UTF-8?q?feat:=2030=20g=C3=BCnl=C3=BCk=20havuz=20referan?= =?UTF-8?q?s=C4=B1=20(kal=C4=B1c=C4=B1=20erime)=20+=2025=20sekt=C3=B6r,=20?= =?UTF-8?q?=C3=A7oklu=20sekt=C3=B6r=20se=C3=A7imi=20ve=20sunucudan=20gelen?= =?UTF-8?q?=20tek=20kaynakl=C4=B1=20liste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/history-store.mjs | 74 +++++++++++++-- server/index.js | 95 ++++++++++++++------ src/components/DetailedSearchModal.jsx | 46 +++++++--- src/components/LeftSidebar.jsx | 57 ++++++++---- src/components/SmartFilterBuilder.jsx | 6 +- src/components/TenderList.jsx | 6 +- src/components/tools/NotifySubscriptions.jsx | 6 +- src/context/AppContext.jsx | 29 +++++- src/data/constants.js | 26 +++++- 9 files changed, 264 insertions(+), 81 deletions(-) diff --git a/server/history-store.mjs b/server/history-store.mjs index 0eb866a..8962709 100644 --- a/server/history-store.mjs +++ b/server/history-store.mjs @@ -253,11 +253,17 @@ export function havuzOlc({ kaynakToplam, yuklenen, dt = null, arsiv = null }) { // 7 günlük referans — tek bir ölçüme değil, ortalamaya kıyaslarız ki // havuz yavaş yavaş erirse de yakalayalım. - const haftaBas = new Date(Date.now() - 7 * 864e5).toISOString(); - const ref = db - .prepare(`SELECT AVG(kaynak_toplam) ort, COUNT(kaynak_toplam) adet - FROM havuz_olcum WHERE zaman >= ? AND kaynak_toplam IS NOT NULL`) - .get(haftaBas) || {}; + const ortalamaAl = (gun) => { + const bas = new Date(Date.now() - gun * 864e5).toISOString(); + return db + .prepare(`SELECT AVG(kaynak_toplam) ort, COUNT(kaynak_toplam) adet + FROM havuz_olcum WHERE zaman >= ? AND kaynak_toplam IS NOT NULL`) + .get(bas) || {}; + }; + // 7 gün: günlük dalgalanma. 30 gün: kalıcı erime — 7 günlük ortalama + // düşüşe ayak uydurup alarmı susturduğunda bunu ancak 30 gün yakalar. + const ref = ortalamaAl(7); + const ref30 = ortalamaAl(30); db.prepare( `INSERT INTO havuz_olcum (zaman, kaynak_toplam, yuklenen, eksik, dt, arsiv) @@ -274,7 +280,7 @@ export function havuzOlc({ kaynakToplam, yuklenen, dt = null, arsiv = null }) { const sinir = new Date(Date.now() - 90 * 864e5).toISOString(); db.prepare('DELETE FROM havuz_olcum WHERE zaman < ?').run(sinir); - return degerlendir(onceki, ref, { kaynakToplam, yuklenen }); + return degerlendir(onceki, ref, ref30, { kaynakToplam, yuklenen }); } finally { db.close(); } @@ -284,19 +290,26 @@ export function havuzOlc({ kaynakToplam, yuklenen, dt = null, arsiv = null }) { const MIN_OLCUM = 5; // Bu oranın altına düşerse uyarılır (7 günlük ortalamanın %60'ı). const DUSUS_ESIGI = 0.6; +// 30 günlük referans için: daha çok ölçüm ister, eşiği daha toleranslıdır — +// amaç ani dalgalanma değil, kalıcı erimeyi görmek. +const MIN_OLCUM_30 = 20; +const DUSUS_ESIGI_30 = 0.7; /** * Ölçümü 7 GÜNLÜK ORTALAMAYA kıyaslar; yeterli geçmiş yoksa bir önceki * ölçüme düşer. Uyarı gerekiyorsa açıklamasını döndürür. */ -function degerlendir(onceki, ref, { kaynakToplam, yuklenen }) { +function degerlendir(onceki, ref, ref30, { kaynakToplam, yuklenen }) { const ortalama = ref?.adet >= MIN_OLCUM && ref.ort ? Math.round(ref.ort) : null; + const ortalama30 = ref30?.adet >= MIN_OLCUM_30 && ref30.ort ? Math.round(ref30.ort) : null; const sonuc = { uyari: null, onceki: onceki?.kaynak_toplam ?? null, simdi: kaynakToplam ?? null, haftalikOrtalama: ortalama, + aylikOrtalama: ortalama30, olcumAdedi: ref?.adet ?? 0, + olcumAdedi30: ref30?.adet ?? 0, }; // 1) Kaynak düştü mü? (bizim hatamız değil ama bilmek isteriz) @@ -321,6 +334,21 @@ function degerlendir(onceki, ref, { kaynakToplam, yuklenen }) { } } + // 1b) Kalıcı erime: 7 günlük ortalama düşüşe ayak uydurduğu için sustuysa + // bile, 30 günlük referans hâlâ çok yukarıdaysa bunu ayrıca bildiririz. + if (kaynakToplam != null && ortalama30 > 0) { + const oran30 = kaynakToplam / ortalama30; + sonuc.aylikOrtalamayaOran = Number(oran30.toFixed(3)); + if (oran30 < DUSUS_ESIGI_30 && !sonuc.uyari) { + sonuc.uyari = `Kaynak havuzu 30 günlük ortalamanın %${Math.round((1 - oran30) * 100)} altında ` + + `(kalıcı düşüş): ortalama ${ortalama30}, şimdi ${kaynakToplam} ilan.`; + sonuc.tur = 'kalici_dusus'; + } else if (oran30 < DUSUS_ESIGI_30 && sonuc.uyari) { + // Zaten haftalık uyarı var; 30 günlük bağlamı mesaja ekle. + sonuc.uyari += ` (30 günlük ortalama ${ortalama30}, oran %${Math.round(oran30 * 100)})`; + } + } + // 2) Biz kaynağın gerisinde mi kaldık? (bu BİZİM hatamız olur) if (!sonuc.uyari && kaynakToplam > 0 && yuklenen != null && yuklenen < kaynakToplam * 0.9) { sonuc.uyari = `Havuz eksik yüklendi: kaynakta ${kaynakToplam}, bizde ${yuklenen} ilan.`; @@ -342,3 +370,35 @@ export function havuzGecmisi(limit = 60) { db.close(); } } + + +/** + * Havuz özeti — 7 ve 30 günlük ortalamalar SQL'de hesaplanır; döndürülen + * satır sayısından bağımsızdır (60 kayıtlık pencereye sıkışmaz). + */ +export function havuzOzet() { + const db = openDb(); + if (!db) return null; + try { + const pencere = (gun) => { + const bas = new Date(Date.now() - gun * 864e5).toISOString(); + const r = db + .prepare(`SELECT AVG(kaynak_toplam) ort, MAX(kaynak_toplam) zirve, MIN(kaynak_toplam) dip, + COUNT(kaynak_toplam) adet + FROM havuz_olcum WHERE zaman >= ? AND kaynak_toplam IS NOT NULL`) + .get(bas) || {}; + return { + ortalama: r.ort ? Math.round(r.ort) : null, + zirve: r.zirve ?? null, + dip: r.dip ?? null, + adet: r.adet ?? 0, + }; + }; + const son = db + .prepare('SELECT zaman, kaynak_toplam, yuklenen, eksik, dt, arsiv FROM havuz_olcum ORDER BY id DESC LIMIT 1') + .get() || null; + return { son, hafta: pencere(7), ay: pencere(30) }; + } finally { + db.close(); + } +} diff --git a/server/index.js b/server/index.js index 8430797..7ff1838 100644 --- a/server/index.js +++ b/server/index.js @@ -24,7 +24,7 @@ import { billingConfigured, webhookConfigured, planCatalog, createCheckoutSessio import * as store from './user-store.mjs'; import { planOf, haklar, kullanim, kotaKontrol, ozellikVar } from './entitlements.mjs'; import { searchTed } from './ted.mjs'; -import { readHistory, historyStats, havuzOlc, havuzGecmisi } from './history-store.mjs'; +import { readHistory, historyStats, havuzOlc, havuzGecmisi, havuzOzet } from './history-store.mjs'; import { startScheduler, schedulerStatus } from './scheduler.mjs'; import { searchKik, kararMetni } from './kik.mjs'; import { documentUrl, ilanMetni, documentText, browserAvailable } from './documents.mjs'; @@ -79,18 +79,40 @@ for (const [region, cities] of Object.entries(REGIONS)) { // --------------------------------------------------------------------------- // Basit sektör / kurum tipi sınıflandırması (başlık ve kurum adına göre) // --------------------------------------------------------------------------- +// Sektör sınıflandırması — sıra ÖNEMLİ: ilk eşleşen kazanır, bu yüzden +// dar/özel kalıplar geniş olanlardan önce gelir (ör. "tıbbi gaz" → Sağlık, +// "doğalgaz" → Elektrik & Enerji). +// UYARI: buradaki etiketler src/data/constants.js SECTORS_LIST ile birebir +// aynı olmalı. Sürüklenmeyi önlemek için liste /api/meta/sektorler ile sunulur. const SECTOR_RULES = [ - [/tıbbi|medikal|sağlık|hastane|ilaç|cerrahi|laboratuvar|diş/i, 'Sağlık / Tıbbi Malzeme'], - [/yazılım|bilişim|sunucu|donanım|kamera|network|bilgisayar/i, 'Bilişim & Yazılım'], - [/gıda|yemek|catering|ekmek|et |sebze|meyve|kumanya/i, 'Gıda & Catering'], - [/temizlik|hijyen|dezenfek|deterjan/i, 'Temizlik & Hijyen'], - [/inşaat|yapım|onarım|tadilat|bakım|asfalt|beton|sulama|yol /i, 'İnşaat & Bakım'], - [/kırtasiye|matbaa|baskı|kağıt|toner/i, 'Kırtasiye & Büro'], - [/giyim|tekstil|elbise|üniforma|kıyafet/i, 'Tekstil & Giyim'], - [/araç|otomotiv|lastik|akaryakıt|yakıt|kiralama/i, 'Otomotiv & Yedek Parça'], - [/elektrik|trafo|enerji|aydınlatma|jeneratör|güneş/i, 'Elektrik & Enerji'], + [/tıbbi|medikal|sağlık|hastane|ilaç|cerrahi|laboratuvar|diş |protez|ortez|görüntüleme|röntgen|tomografi|steril|enjektör|eldiven|maske|reaktif|kit alım/i, 'Sağlık / Tıbbi Malzeme'], + [/yazılım|bilişim|sunucu|donanım|network|bilgisayar|lisans|veri merkezi|siber|yazıcı alım|tablet|projeksiyon/i, 'Bilişim & Yazılım'], + [/güvenlik hizmet|özel güvenlik|kamera|cctv|alarm|turnike|x-ray|güvenlik sistem/i, 'Güvenlik Sistemleri'], + [/gıda|yemek|catering|ekmek|et alım|sebze|meyve|kumanya|süt|tavuk|bakliyat|mutfak/i, 'Gıda & Catering'], + [/temizlik|hijyen|dezenfek|deterjan|çöp|atık toplama|haşere|ilaçlama/i, 'Temizlik & Hijyen'], + [/asfalt|beton|yol yapım|köprü|altyapı|kanalizasyon|içme suyu|sulama|menfez|istinat/i, 'Altyapı & Yol'], + [/inşaat|yapım işi|onarım|tadilat|güçlendirme|çatı|cephe|restorasyon|bakım onarım/i, 'İnşaat & Bakım'], + [/kırtasiye|matbaa|baskı|kağıt|toner|kartuş|fotokopi|büro malzeme/i, 'Kırtasiye & Büro'], + [/mobilya|masa|sandalye|dolap|okul sırası|büro mobilya|raf /i, 'Mobilya & Donatı'], + [/giyim|tekstil|elbise|üniforma|kıyafet|ayakkabı|çamaşır|kumaş|battaniye/i, 'Tekstil & Giyim'], + [/akaryakıt|motorin|benzin|lpg|yakıt alım/i, 'Akaryakıt & Yakıt'], + [/araç kirala|taşıt kirala|servis hizmet|personel taşıma|öğrenci taşıma|nakliye|lojistik/i, 'Taşımacılık & Kiralama'], + [/araç|otomotiv|lastik|yedek parça|otobüs|kamyon|iş makinesi|greyder|ekskavatör/i, 'Otomotiv & İş Makinesi'], + [/elektrik|trafo|enerji|aydınlatma|jeneratör|güneş|solar|kablo|pano |ups /i, 'Elektrik & Enerji'], + [/doğalgaz|kalorifer|kazan|ısıtma|soğutma|klima|havalandırma|iklimlendirme|mekanik tesisat/i, 'Isıtma & İklimlendirme'], + [/asansör|yürüyen merdiven/i, 'Asansör & Yürüyen Sistem'], + [/su arıtma|arıtma tesis|pompa|sondaj|kuyu |su deposu/i, 'Su & Arıtma'], + [/tarım|tohum|gübre|fide|zirai|hayvan|yem |veteriner|sera /i, 'Tarım & Hayvancılık'], + [/peyzaj|park bahçe|ağaç|çim |fidan|oyun grubu|kent mobilya/i, 'Peyzaj & Park'], + [/eğitim hizmet|kurs|seminer|danışmanlık|müşavirlik|etüt proje|mühendislik hizmet|kontrollük/i, 'Danışmanlık & Proje'], + [/reklam|tanıtım|organizasyon|fuar|basın|medya|prodüksiyon/i, 'Reklam & Organizasyon'], + [/sigorta|zorunlu trafik|kasko/i, 'Sigorta'], + [/kitap|yayın|kütüphane|arşiv|dijitalleştirme/i, 'Yayın & Arşiv'], + [/spor|halı saha|tribün|fitness|kondisyon/i, 'Spor Tesisleri'], + [/laboratuvar cihaz|analiz cihaz|ölçüm cihaz|test cihaz/i, 'Laboratuvar & Ölçüm'], ]; + const INSTITUTION_RULES = [ [/büyükşehir belediye/i, 'Büyükşehir Belediyeleri'], [/belediye/i, 'Belediyeler'], @@ -102,6 +124,13 @@ const INSTITUTION_RULES = [ [/a\.ş\.|genel müdürlüğü|işletme/i, 'Kamu Şirketleri'], ]; +// Arayüzün kullandığı sektör listesi buradan türetilir — elle tutulan ikinci +// bir liste tutmayız, böylece iki taraf birbirinden ayrı düşemez. +const SEKTOR_ETIKETLERI = [...new Set(SECTOR_RULES.map(([, ad]) => ad))].sort((a, b) => + a.localeCompare(b, 'tr')); +const KURUM_ETIKETLERI = [...new Set(INSTITUTION_RULES.map(([, ad]) => ad))].sort((a, b) => + a.localeCompare(b, 'tr')); + function classify(rules, text, fallback) { for (const [re, label] of rules) if (re.test(text)) return label; return fallback; @@ -965,29 +994,39 @@ const server = http.createServer(async (req, res) => { } if (url.pathname === '/api/health') return json(res, 200, { ok: true }); + if (url.pathname === '/api/meta/filtreler') { + return json(res, 200, { + success: true, + sektorler: SEKTOR_ETIKETLERI, + kurumTipleri: KURUM_ETIKETLERI, + }); + } + if (url.pathname === '/api/health/havuz') { - const gecmis = havuzGecmisi(Number(url.searchParams.get('limit')) || 60); - const son = gecmis[0] || null; - // Son 7 günün en yükseği — bugünkü sayı buna göre kıyaslanır. - const haftaBas = new Date(Date.now() - 7 * 864e5).toISOString(); - const hafta = gecmis.filter((r) => r.zaman >= haftaBas && r.kaynak_toplam != null); - const zirve = hafta.reduce((m, r) => Math.max(m, r.kaynak_toplam || 0), 0); - // Referans ölçüt ORTALAMA; zirve yalnızca bağlam için gösterilir. - const ortalama = hafta.length ? Math.round(hafta.reduce((t, r) => t + r.kaynak_toplam, 0) / hafta.length) : null; - const yeterliVeri = hafta.length >= 5; + const ozet = havuzOzet(); + if (!ozet) return json(res, 200, { success: true, son: null, olcumSayisi: 0, gecmis: [] }); + const { son, hafta, ay } = ozet; + const oran = (ort, yeterli) => + yeterli && son?.kaynak_toplam && ort ? Number((son.kaynak_toplam / ort).toFixed(3)) : null; + const yeterliVeri = hafta.adet >= 5; + const yeterliVeri30 = ay.adet >= 20; return json(res, 200, { success: true, son, - haftalikOrtalama: ortalama, - haftalikZirve: zirve || null, - olcumAdedi: hafta.length, + // 7 gün: günlük dalgalanma + haftalikOrtalama: hafta.ortalama, + haftalikZirve: hafta.zirve, + olcumAdedi: hafta.adet, yeterliVeri, - ortalamayaOran: - yeterliVeri && son?.kaynak_toplam && ortalama - ? Number((son.kaynak_toplam / ortalama).toFixed(3)) - : null, - olcumSayisi: gecmis.length, - gecmis, + ortalamayaOran: oran(hafta.ortalama, yeterliVeri), + // 30 gün: kalıcı erime + aylikOrtalama: ay.ortalama, + aylikZirve: ay.zirve, + aylikDip: ay.dip, + olcumAdedi30: ay.adet, + yeterliVeri30, + aylikOrtalamayaOran: oran(ay.ortalama, yeterliVeri30), + gecmis: havuzGecmisi(Number(url.searchParams.get('limit')) || 60), }); } diff --git a/src/components/DetailedSearchModal.jsx b/src/components/DetailedSearchModal.jsx index e1aa40d..3b051e7 100644 --- a/src/components/DetailedSearchModal.jsx +++ b/src/components/DetailedSearchModal.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useApp } from '../context/AppContext'; -import { SECTORS_LIST, CITIES_LIST, INSTITUTION_TYPES_LIST } from '../data/constants'; +import { CITIES_LIST, INSTITUTION_TYPES_LIST } from '../data/constants'; import { SlidersHorizontal, Search, RotateCcw, X } from 'lucide-react'; export const DetailedSearchModal = () => { @@ -13,8 +13,10 @@ export const DetailedSearchModal = () => { setSelectedCity, selectedAlimTuru, setSelectedAlimTuru, - selectedSektor, - setSelectedSektor, + selectedSektorler, + setSelectedSektorler, + toggleSektor, + sektorListesi, selectedKurumTipi, setSelectedKurumTipi, onlyValidOnly, @@ -40,7 +42,7 @@ export const DetailedSearchModal = () => { setSearchQuery(''); setSelectedCity(''); setSelectedAlimTuru(''); - setSelectedSektor(''); + setSelectedSektorler([]); setSelectedKurumTipi(''); setIknQuery(''); setKurumQuery(''); @@ -116,16 +118,32 @@ export const DetailedSearchModal = () => { /> -
- - +
+ +
+ {sektorListesi.map((s) => { + const secili = selectedSektorler.includes(s); + return ( + + ); + })} +
diff --git a/src/components/LeftSidebar.jsx b/src/components/LeftSidebar.jsx index c168a27..2ab2868 100644 --- a/src/components/LeftSidebar.jsx +++ b/src/components/LeftSidebar.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useApp } from '../context/AppContext'; -import { SECTORS_LIST, INSTITUTION_TYPES_LIST, CITIES_LIST } from '../data/constants'; +import { INSTITUTION_TYPES_LIST, CITIES_LIST } from '../data/constants'; import { Compass, Zap, Wrench, Calendar, Eye, EyeOff, FileText, CheckCircle2, XCircle, Archive, Bot, PlusCircle, Trash2, ChevronDown, ChevronUp, Filter, MapPin, Building2, Tag, Star @@ -16,8 +16,10 @@ export const LeftSidebar = () => { deleteSavedFilter, selectedAlimTuru, setSelectedAlimTuru, - selectedSektor, - setSelectedSektor, + selectedSektorler, + setSelectedSektorler, + toggleSektor, + sektorListesi, selectedKurumTipi, setSelectedKurumTipi, selectedCity, @@ -25,6 +27,7 @@ export const LeftSidebar = () => { clearAllFilters } = useApp(); + const [sektorAra, setSektorAra] = useState(''); const [openSections, setOpenSections] = useState({ alimTuru: true, sektorler: true, @@ -59,7 +62,7 @@ export const LeftSidebar = () => { Hızlı Erişim - {(quickFilter !== 'all' || selectedSektor || selectedCity || selectedAlimTuru || selectedKurumTipi) && ( + {(quickFilter !== 'all' || selectedSektorler.length || selectedCity || selectedAlimTuru || selectedKurumTipi) && ( {openSections.sektorler && ( -
+
+ {/* 25 sektör var; aramadan bulmak zor */} + setSektorAra(e.target.value)} + placeholder="Sektör ara…" + className="w-full border border-slate-200 rounded-lg px-2 py-1.5 mb-1 text-xs focus:outline-hidden focus:ring-2 focus:ring-blue-500/30" + /> +
- {SECTORS_LIST.map(sec => ( - - ))} + {/* Çoklu seçim: kutucuklar birikir, liste kapanmaz */} + {sektorListesi + .filter((sec) => sec.toLocaleLowerCase('tr').includes(sektorAra.toLocaleLowerCase('tr'))) + .map(sec => { + const secili = selectedSektorler.includes(sec); + return ( + + ); + })} +
)}
diff --git a/src/components/SmartFilterBuilder.jsx b/src/components/SmartFilterBuilder.jsx index 5285ff6..afeef53 100644 --- a/src/components/SmartFilterBuilder.jsx +++ b/src/components/SmartFilterBuilder.jsx @@ -1,10 +1,10 @@ import React, { useState } from 'react'; import { useApp } from '../context/AppContext'; -import { INSTITUTION_TYPES_LIST, CITIES_LIST, SECTORS_LIST } from '../data/constants'; +import { INSTITUTION_TYPES_LIST, CITIES_LIST } from '../data/constants'; import { Bot, ArrowLeft, CheckCircle2, HelpCircle, Building2, MapPin, Tag } from 'lucide-react'; export const SmartFilterBuilder = () => { - const { savedFilters, addSavedFilter, deleteSavedFilter, setActiveTab } = useApp(); + const { savedFilters, addSavedFilter, deleteSavedFilter, setActiveTab, sektorListesi } = useApp(); const [filterName, setFilterName] = useState(''); const [selectedKurumTip, setSelectedKurumTip] = useState(''); @@ -238,7 +238,7 @@ export const SmartFilterBuilder = () => { className="w-full bg-slate-50 border border-slate-300 rounded-lg px-3 py-2 text-xs text-slate-800" > - {SECTORS_LIST.map(s => ( + {sektorListesi.map(s => ( ))} diff --git a/src/components/TenderList.jsx b/src/components/TenderList.jsx index e3cc009..4c6651f 100644 --- a/src/components/TenderList.jsx +++ b/src/components/TenderList.jsx @@ -14,7 +14,7 @@ export const TenderList = () => { searchQuery, selectedCity, selectedAlimTuru, - selectedSektor, + selectedSektorler, selectedKurumTipi, quickFilter, onlyValidOnly, @@ -52,7 +52,7 @@ export const TenderList = () => { // Dropdown Filters if (selectedCity && normTr(tender.sehir) !== normTr(selectedCity)) return false; if (selectedAlimTuru && tender.alimTuru !== selectedAlimTuru) return false; - if (selectedSektor && tender.sektor !== selectedSektor) return false; + if (selectedSektorler.length && !selectedSektorler.includes(tender.sektor)) return false; if (selectedKurumTipi && tender.kurumTipi !== selectedKurumTipi) return false; return true; @@ -62,7 +62,7 @@ export const TenderList = () => { if (sortBy === 'distant') return b.tarih.localeCompare(a.tarih); return 0; }); - }, [tenders, searchQuery, selectedCity, selectedAlimTuru, selectedSektor, selectedKurumTipi, quickFilter, sortBy]); + }, [tenders, searchQuery, selectedCity, selectedAlimTuru, selectedSektorler, selectedKurumTipi, quickFilter, sortBy]); const totalPages = Math.max(1, Math.ceil(filteredTenders.length / itemsPerPage)); const paginatedTenders = filteredTenders.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage); diff --git a/src/components/tools/NotifySubscriptions.jsx b/src/components/tools/NotifySubscriptions.jsx index 26aafc9..3fca010 100644 --- a/src/components/tools/NotifySubscriptions.jsx +++ b/src/components/tools/NotifySubscriptions.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useApp } from '../../context/AppContext'; import { getToken } from '../../services/auth'; -import { SECTORS_LIST } from '../../data/constants'; + import { BellRing, Plus, Trash2, Loader2, AlertTriangle, Info, Globe, Sparkles, ChevronDown, ChevronUp, X } from 'lucide-react'; /** @@ -10,7 +10,7 @@ import { BellRing, Plus, Trash2, Loader2, AlertTriangle, Info, Globe, Sparkles, * isteğe bağlıdır (varsayılan: tüm Türkiye). */ export const NotifySubscriptions = () => { - const { authUser, favoriteKurumlar, userProfile } = useApp(); + const { authUser, favoriteKurumlar, userProfile, sektorListesi } = useApp(); const [abonelikler, setAbonelikler] = useState(null); const [busy, setBusy] = useState(false); @@ -212,7 +212,7 @@ export const NotifySubscriptions = () => {

Hazır sektörler:

- {SECTORS_LIST.map((s) => ( + {sektorListesi.map((s) => (