diff --git a/src/context/AppContext.jsx b/src/context/AppContext.jsx
index 7de80c3..0ce6101 100644
--- a/src/context/AppContext.jsx
+++ b/src/context/AppContext.jsx
@@ -1,3 +1,4 @@
+import {savedFilterCriteria} from '../services/savedFilterCriteria';
import {mergeServerNotifications,acknowledgeNotifications} from '../services/notifications';
import { searchTender } from '../services/searchTender';
import { normalizeTenderSearch, matchingItemResults } from '../services/tenderSearch';
@@ -80,6 +81,7 @@ export const AppProvider = ({ children }) => {
return () => { iptal = true; controller.abort(); clearTimeout(zaman); };
}, [searchQuery,kalemRetry]);
const [selectedCity, setSelectedCity] = useState('');
+ const [selectedRegion,setSelectedRegion]=useState('');
const [selectedAlimTuru, setSelectedAlimTuru] = useState('');
const [selectedKurumTipi, setSelectedKurumTipi] = useState('');
// Çoklu sektör seçimi. Tek seçimlik eski API (selectedSektor) uyumluluk
@@ -689,7 +691,19 @@ export const AppProvider = ({ children }) => {
const addSavedFilter = newFilter => persistSavedFilters([{...newFilter,id:'sf-'+crypto.randomUUID(),user:userProfile.username},...savedFilters]);
const deleteSavedFilter = id => persistSavedFilters(savedFilters.filter(filter=>filter.id!==id));
+ const applySavedFilter = filter => {
+ try {
+ const criteria=savedFilterCriteria(filter);
+ setSearchQuery('');setSelectedCpvler([]);setQuickFilter('all');
+ setSelectedCity(criteria.city);setSelectedRegion(criteria.region);
+ setSelectedAlimTuru(criteria.procedure);setSelectedKurumTipi(criteria.institution);
+ setSelectedSektorler(criteria.sector?[criteria.sector]:[]);setActiveTab('list');setSaveError(null);
+ return true;
+ }catch(error){setSaveError(error.message);return false;}
+ };
+
const clearAllFilters = () => {
+ setSelectedRegion('');
setSearchQuery('');
setSelectedCity('');
setSelectedAlimTuru('');
@@ -723,7 +737,7 @@ export const AppProvider = ({ children }) => {
searchQuery,
kalemEslesen, itemSearchState, retryItemSearch,
setSearchQuery,
- selectedCity,
+ selectedCity, selectedRegion,setSelectedRegion,applySavedFilter,
setSelectedCity,
selectedAlimTuru,
setSelectedAlimTuru,
diff --git a/src/services/savedFilterCriteria.js b/src/services/savedFilterCriteria.js
new file mode 100644
index 0000000..5f80fc1
--- /dev/null
+++ b/src/services/savedFilterCriteria.js
@@ -0,0 +1,19 @@
+const norm=value=>String(value || '').normalize('NFC').trim().toLocaleUpperCase('tr-TR');
+export const REGION_CITIES={
+ 'MARMARA BÖLGESİ':'Balıkesir,Bilecik,Bursa,Çanakkale,Edirne,İstanbul,Kırklareli,Kocaeli,Sakarya,Tekirdağ,Yalova',
+ 'EGE BÖLGESİ':'Afyonkarahisar,Aydın,Denizli,İzmir,Kütahya,Manisa,Muğla,Uşak',
+ 'AKDENİZ BÖLGESİ':'Adana,Antalya,Burdur,Hatay,Isparta,Kahramanmaraş,Mersin,Osmaniye',
+ 'İÇ ANADOLU BÖLGESİ':'Aksaray,Ankara,Çankırı,Eskişehir,Karaman,Kayseri,Kırıkkale,Kırşehir,Konya,Nevşehir,Niğde,Sivas,Yozgat',
+ 'KARADENİZ BÖLGESİ':'Amasya,Artvin,Bartın,Bayburt,Bolu,Çorum,Düzce,Giresun,Gümüşhane,Karabük,Kastamonu,Ordu,Rize,Samsun,Sinop,Tokat,Trabzon,Zonguldak',
+ 'DOĞU ANADOLU BÖLGESİ':'Ağrı,Ardahan,Bingöl,Bitlis,Elazığ,Erzincan,Erzurum,Hakkari,Iğdır,Kars,Malatya,Muş,Tunceli,Van',
+ 'GÜNEYDOĞU ANADOLU BÖLGESİ':'Adıyaman,Batman,Diyarbakır,Gaziantep,Kilis,Mardin,Siirt,Şanlıurfa,Şırnak',
+};
+const cityRegions=new Map(Object.entries(REGION_CITIES).flatMap(([region,cities])=>cities.split(',').map(city=>[norm(city),region])));
+export function matchesRegion(tender,region){return !region || cityRegions.get(norm(tender.sehir))===norm(region);}
+export function matchesProcurement(tender,procedure){return !procedure || [tender.usul,tender.ihaleUsulu,tender.alimTuru].some(value=>norm(value)===norm(procedure));}
+export function savedFilterCriteria(filter){
+ const read=key=>{if(filter[key]!=null&&typeof filter[key]!=='string')throw new Error('Kayıtlı filtre ölçütü geçersiz.');return (filter[key] || '').trim();};
+ const region=read('bolge');
+ if(region&&!Object.keys(REGION_CITIES).includes(norm(region)))throw new Error('Kayıtlı bölge tanınmadı; filtreyi yeniden düzenleyin.');
+ return {city:read('sehir'),region:region?norm(region):'',sector:read('sektor'),institution:read('kurumTipi'),procedure:read('usul')};
+}
diff --git a/tests/saved-filter-criteria.test.mjs b/tests/saved-filter-criteria.test.mjs
new file mode 100644
index 0000000..72da459
--- /dev/null
+++ b/tests/saved-filter-criteria.test.mjs
@@ -0,0 +1,21 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import {REGION_CITIES,matchesRegion,matchesProcurement,savedFilterCriteria} from '../src/services/savedFilterCriteria.js';
+import {CITIES_LIST} from '../src/data/constants.js';
+test('province-based regions cover all 81 cities exactly once and normalize Turkish names',()=>{
+ const cities=Object.values(REGION_CITIES).flatMap(value=>value.split(','));
+ assert.equal(cities.length,81);assert.deepEqual([...cities].sort(),[...CITIES_LIST].sort());assert.equal(new Set(cities).size,81);
+ assert.equal(matchesRegion({sehir:' giresun '},'KARADENİZ BÖLGESİ'),true);
+ assert.equal(matchesRegion({sehir:'Kayseri'},'KARADENİZ BÖLGESİ'),false);
+ assert.equal(matchesRegion({sehir:''},'KARADENİZ BÖLGESİ'),false);
+});
+test('saved criteria preserves all five fields and rejects unrecognized regions',()=>{
+ assert.deepEqual(savedFilterCriteria({sehir:' Giresun ',bolge:'karadeniz bölgesi',sektor:'Laboratuvar',kurumTipi:'Hastaneler',usul:'Açık İhale'}),{city:'Giresun',region:'KARADENİZ BÖLGESİ',sector:'Laboratuvar',institution:'Hastaneler',procedure:'Açık İhale'});
+ assert.throws(()=>savedFilterCriteria({bolge:'Bilinmeyen'}),/tanınmadı/);
+ assert.throws(()=>savedFilterCriteria({sehir:[]}),/geçersiz/);
+});
+test('procedure filter uses explicit source procedure, not inferred purchase category',()=>{
+ assert.equal(matchesProcurement({alimTuru:'Mal Alımı',ihaleUsulu:'Açık İhale'},'Açık İhale'),true);
+ assert.equal(matchesProcurement({alimTuru:'Mal Alımı'},'Açık İhale'),false);
+ assert.equal(matchesProcurement({alimTuru:'Doğrudan Temin'},'Doğrudan Temin'),true);
+});