feat: İhaleist - Akıllı İhale Takip ve Analiz Platformu (Full PRD + 7 Enterprise Modül)
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { MOCK_TENDERS, MOCK_LIVE_STATS, MOCK_COMPANY_STATS } from '../data/mockTenders';
|
||||
|
||||
const AppContext = createContext();
|
||||
|
||||
export const AppProvider = ({ children }) => {
|
||||
const [activeTab, setActiveTab] = useState('list'); // 'list', 'bulten', 'sonuc', 'takvim', 'sozlesme', 'kdv', 'fiyat', 'kanban', 'smart_filter_builder'
|
||||
const [tenders, setTenders] = useState(() => {
|
||||
const saved = localStorage.getItem('ihaleist_tenders');
|
||||
return saved ? JSON.parse(saved) : MOCK_TENDERS;
|
||||
});
|
||||
|
||||
const [liveStats, setLiveStats] = useState(MOCK_LIVE_STATS);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedCity, setSelectedCity] = useState('');
|
||||
const [selectedAlimTuru, setSelectedAlimTuru] = useState('');
|
||||
const [selectedKurumTipi, setSelectedKurumTipi] = useState('');
|
||||
const [selectedSektor, setSelectedSektor] = useState('');
|
||||
const [onlyValidOnly, setOnlyValidOnly] = useState(true);
|
||||
const [quickFilter, setQuickFilter] = useState('all');
|
||||
|
||||
const [savedFilters, setSavedFilters] = useState(() => {
|
||||
const saved = localStorage.getItem('ihaleist_saved_filters');
|
||||
return saved ? JSON.parse(saved) : [
|
||||
{ id: 'sf-1', name: 'Sağlık Hizmeti İhaleleri', user: 'hikmet', kurumTipi: 'Eğitim ve Araştırma Hastaneleri', sehir: 'Ankara' },
|
||||
{ id: 'sf-2', name: 'Kayseri Kırtasiye Alımları', user: 'hikmet', kurumTipi: 'Ceza İnfaz Kurumları', sehir: 'Kayseri' }
|
||||
];
|
||||
});
|
||||
|
||||
const [activeTenderDetail, setActiveTenderDetail] = useState(null);
|
||||
const [isDetailedSearchOpen, setIsDetailedSearchOpen] = useState(false);
|
||||
const [isCompanyScannerOpen, setIsCompanyScannerOpen] = useState(false);
|
||||
const [isAiSpecAnalyzerOpen, setIsAiSpecAnalyzerOpen] = useState(false);
|
||||
|
||||
const [aiAnalysisResults, setAiAnalysisResults] = useState(null);
|
||||
|
||||
// Enterprise Module States
|
||||
const [competitorWatchlist, setCompetitorWatchlist] = useState([
|
||||
{ id: 'cw-1', name: 'Bileşim Tıbbi Cihazlar A.Ş.', sector: 'Medikal & Tıbbi Malzeme', winCount: 24, totalRevenue: 14200000, marketShare: 32, status: 'Aktif İzleniyor', alertOnBid: true, alertOnWin: true },
|
||||
{ id: 'cw-2', name: 'Atlas Sağlık ve Kırtasiye Ltd.', sector: 'Sağlık & Büro Malzemeleri', winCount: 18, totalRevenue: 9800000, marketShare: 24, status: 'Aktif İzleniyor', alertOnBid: true, alertOnWin: true },
|
||||
{ id: 'cw-3', name: 'Tekno-Med Laboratuvar Sistemleri', sector: 'Laboratuvar & Bilişim', winCount: 12, totalRevenue: 6500000, marketShare: 16, status: 'Aktif İzleniyor', alertOnBid: false, alertOnWin: true }
|
||||
]);
|
||||
|
||||
const [vaultDocuments, setVaultDocuments] = useState([
|
||||
{ id: 'doc-1', name: 'İmza Sirküleri 2026.pdf', category: 'Yasal Belgeler', size: '2.4 MB', updatedAt: '2026-01-15', expiresAt: '2027-01-15', status: 'Geçerli' },
|
||||
{ id: 'doc-2', name: 'ISO 13485 Tıbbi Cihaz Sertifikası.pdf', category: 'Kalite Belgeleri', size: '1.8 MB', updatedAt: '2025-11-10', expiresAt: '2028-11-10', status: 'Geçerli' },
|
||||
{ id: 'doc-3', name: 'Oda Faaliyet Belgesi.pdf', category: 'Resmi Kayıtlar', size: '1.1 MB', updatedAt: '2026-02-01', expiresAt: '2026-08-01', status: 'Geçerli' },
|
||||
{ id: 'doc-4', name: 'ÜTS Firma Kayıt Belgesi.pdf', category: 'Tıbbi Cihaz Kaydı', size: '3.2 MB', updatedAt: '2026-03-20', expiresAt: '2027-03-20', status: 'Geçerli' },
|
||||
{ id: 'doc-5', name: 'Vergi Levhası 2025.pdf', category: 'Mali Belgeler', size: '850 KB', updatedAt: '2025-05-30', expiresAt: '2026-05-30', status: 'Geçerli' }
|
||||
]);
|
||||
|
||||
const [reminderSettings, setReminderSettings] = useState({
|
||||
emailEnabled: true,
|
||||
smsEnabled: true,
|
||||
pushEnabled: true,
|
||||
notify3DaysBefore: true,
|
||||
notify1DayBefore: true,
|
||||
notify3HoursBefore: true,
|
||||
googleCalendarSync: true,
|
||||
outlookSync: false
|
||||
});
|
||||
|
||||
const [webhooksConfig, setWebhooksConfig] = useState({
|
||||
slackWebhookUrl: 'https://hooks.slack.com/services/T00/B00/XXXXXX',
|
||||
teamsWebhookUrl: 'https://outlook.office.com/webhook/XXXXXX',
|
||||
events: {
|
||||
newTenderMatch: true,
|
||||
tenderWon: true,
|
||||
bidDeadlineAlert: true,
|
||||
competitorActivity: true
|
||||
}
|
||||
});
|
||||
|
||||
const [apiKeys, setApiKeys] = useState([
|
||||
{ id: 'key-1', name: 'ERP Entegrasyon Anahtarı', key: 'ih_live_8f92a19c3b88412e', createdAt: '2026-04-10', lastUsed: 'Bugün 12:45' }
|
||||
]);
|
||||
|
||||
const analyzeSpecPdfWithGemini = (fileName = 'Teknik_Sartname.pdf') => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const result = {
|
||||
fileName,
|
||||
model: 'Gemini 3.6 Flash',
|
||||
analyzedAt: new Date().toLocaleString('tr-TR'),
|
||||
summary: 'İşbu teknik şartname, idarenin ihtiyacı olan medikal sarf malzemeleri ve cihaz parçalarının teknik özellikleri, garanti şartları, teslimat vadeleri ve muayene kabul kriterlerini düzenlemektedir.',
|
||||
qualificationCriteria: [
|
||||
'TSE HYB Belgesi ve Yetkili Satıcı Satış Yetki Belgesi',
|
||||
'ISO 13485 Medikal Cihaz Kalite Yönetim Sertifikası',
|
||||
'ÜTS (Ürün Takip Sistemi) Barkod ve TİUBB Kaydı',
|
||||
'En az %50 Oranında Benzer İş Deneyim Belgesi (Kamu/Özel)',
|
||||
'Son 3 yıla ait Bilanço ve Gelir Tablosu oranları'
|
||||
],
|
||||
criticalRequirements: [
|
||||
'Ürünler biyo-uyumluluk (ISO 10993) standartlarına uygun olmalıdır.',
|
||||
'Teslim edilen paketlerin sterilizasyon son kullanma tarihi en az 18 ay olmalıdır.',
|
||||
'Arıza durumunda 24 saat içinde yerinde teknik servis desteği sağlanmalıdır.',
|
||||
'Teslimat aşamasında her parti için numune test raporu (CoA) ibraz edilmelidir.'
|
||||
],
|
||||
risksAndPenalties: [
|
||||
{ level: 'Yüksek', clause: 'Madde 14.2', text: 'Gecikilen her takvim günü için ihale bedelinin %0.5 oranında cezai şart uygulanacaktır.' },
|
||||
{ level: 'Orta', clause: 'Madde 18.1', text: 'Numune kontrolünde standart dışı bulunan partiler 3 iş günü içinde muadili ile ücretsiz değiştirilmelidir.' },
|
||||
{ level: 'Düşük', clause: 'Madde 22 border', text: 'Ambalaj üzerindeki Türkçe etiket eksikliğinde kabul işlemi durdurulacaktır.' }
|
||||
],
|
||||
bidAnalysis: {
|
||||
estimatedCost: 185000,
|
||||
recommendedBid: 148500,
|
||||
minBidLimit: 132000,
|
||||
maxBidLimit: 165000,
|
||||
winProbability: 89,
|
||||
competitionLevel: 'Orta Yoğunlukta (3-4 Firma Katılımı Bekleniyor)',
|
||||
historicalDiscountAvg: '%19.7 EKAP İndirim Oranı'
|
||||
}
|
||||
};
|
||||
|
||||
setAiAnalysisResults(result);
|
||||
resolve(result);
|
||||
}, 2200);
|
||||
});
|
||||
};
|
||||
|
||||
const [userProfile, setUserProfile] = useState({
|
||||
name: 'Hikmet Tanrıverdi',
|
||||
username: 'tanriverdi',
|
||||
company: 'Tanrıverdi Medikal & Teknolojileri A.Ş.',
|
||||
website: 'https://www.tanriverdimedikal.com.tr',
|
||||
trialDaysLeft: 7,
|
||||
role: 'Yönetici Tedarikçi',
|
||||
scrapedCapabilities: {
|
||||
scrapedAt: '2026-08-07 14:30',
|
||||
extractedSectors: ['Sağlık / Tıbbi Malzeme', 'Kırtasiye & Büro', 'Temizlik & Hijyen'],
|
||||
extractedProducts: ['Ortopedi Protez Setleri', 'Cerrahi Maske', 'Dezenfektan', 'Hasta Yatağı', 'Sanatsal Kırtasiye'],
|
||||
extractedCertifications: ['ISO 13485 Tıbbi Cihaz Kalite', 'ISO 9001', 'ÜTS Ürün Kayıt Yetkisi', 'CE Uygunluk'],
|
||||
matchScoreBoost: 15
|
||||
}
|
||||
});
|
||||
|
||||
const scanCompanyWebsite = (url) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const domain = url.replace(/https?:\/\//, '').replace(/\/.*$/, '');
|
||||
const newCapabilities = {
|
||||
scrapedAt: new Date().toLocaleString('tr-TR'),
|
||||
extractedSectors: ['Sağlık / Tıbbi Malzeme', 'Bilişim & Yazılım', 'Kırtasiye & Büro'],
|
||||
extractedProducts: [
|
||||
`${domain} - Medikal Cihaz ve Sarfları`,
|
||||
'Laboratuvar Test Kiti',
|
||||
'Zayıf Akım Güvenlik Sistemleri',
|
||||
'Ofis & Büro Kırtasiye Malzemeleri'
|
||||
],
|
||||
extractedCertifications: [
|
||||
'ISO 13485 Medikal Kalite',
|
||||
'ISO 27001 Bilgi Güvenliği',
|
||||
'ÜTS Ürün Takip Sistemi Belgesi',
|
||||
'TSE Hizmet Yeterlilik Belgesi'
|
||||
],
|
||||
matchScoreBoost: 20
|
||||
};
|
||||
|
||||
setUserProfile(prev => ({
|
||||
...prev,
|
||||
website: url,
|
||||
scrapedCapabilities: newCapabilities
|
||||
}));
|
||||
|
||||
resolve(newCapabilities);
|
||||
}, 2500);
|
||||
});
|
||||
};
|
||||
|
||||
const [notifications, setNotifications] = useState([
|
||||
{ id: 'n1', title: 'Yeni İhale Bildirimi', text: 'Sağlık Hizmeti İhaleleri filtrenize 3 yeni ihale eklendi.', time: '5dk önce', read: false },
|
||||
{ id: 'n2', title: 'Yaklaşan İhale Süresi', text: 'KIRTASİYE MALZEMELERİ ALIMI ihaleniz için son 3 gün!', time: '1sa önce', read: false }
|
||||
]);
|
||||
|
||||
// Persist state to localstorage
|
||||
useEffect(() => {
|
||||
localStorage.setItem('ihaleist_tenders', JSON.stringify(tenders));
|
||||
}, [tenders]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('ihaleist_saved_filters', JSON.stringify(savedFilters));
|
||||
}, [savedFilters]);
|
||||
|
||||
// Live counter animation effect
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setLiveStats(prev => ({
|
||||
...prev,
|
||||
onlineKullanici: prev.onlineKullanici + (Math.floor(Math.random() * 5) - 2),
|
||||
bugunEklenen: prev.bugunEklenen + (Math.random() > 0.7 ? 1 : 0)
|
||||
}));
|
||||
}, 4000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const toggleTrackTender = (tenderId) => {
|
||||
setTenders(prev => prev.map(t => {
|
||||
if (t.id === tenderId) {
|
||||
return { ...t, takipte: !t.takipte };
|
||||
}
|
||||
return t;
|
||||
}));
|
||||
};
|
||||
|
||||
const updateKanbanStatus = (tenderId, newStatus, assignedUser = null) => {
|
||||
setTenders(prev => prev.map(t => {
|
||||
if (t.id === tenderId) {
|
||||
return {
|
||||
...t,
|
||||
kanbanStatus: newStatus,
|
||||
atananPersonel: assignedUser !== null ? assignedUser : t.atananPersonel,
|
||||
takipte: true
|
||||
};
|
||||
}
|
||||
return t;
|
||||
}));
|
||||
};
|
||||
|
||||
const addSavedFilter = (newFilter) => {
|
||||
const filterObj = {
|
||||
id: 'sf-' + Date.now(),
|
||||
user: userProfile.username,
|
||||
...newFilter
|
||||
};
|
||||
setSavedFilters(prev => [filterObj, ...prev]);
|
||||
};
|
||||
|
||||
const deleteSavedFilter = (id) => {
|
||||
setSavedFilters(prev => prev.filter(f => f.id !== id));
|
||||
};
|
||||
|
||||
const clearAllFilters = () => {
|
||||
setSearchQuery('');
|
||||
setSelectedCity('');
|
||||
setSelectedAlimTuru('');
|
||||
setSelectedKurumTipi('');
|
||||
setSelectedSektor('');
|
||||
setQuickFilter('all');
|
||||
};
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
tenders,
|
||||
setTenders,
|
||||
liveStats,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
selectedCity,
|
||||
setSelectedCity,
|
||||
selectedAlimTuru,
|
||||
setSelectedAlimTuru,
|
||||
selectedKurumTipi,
|
||||
setSelectedKurumTipi,
|
||||
selectedSektor,
|
||||
setSelectedSektor,
|
||||
onlyValidOnly,
|
||||
setOnlyValidOnly,
|
||||
quickFilter,
|
||||
setQuickFilter,
|
||||
savedFilters,
|
||||
addSavedFilter,
|
||||
deleteSavedFilter,
|
||||
activeTenderDetail,
|
||||
setActiveTenderDetail,
|
||||
isDetailedSearchOpen,
|
||||
setIsDetailedSearchOpen,
|
||||
isCompanyScannerOpen,
|
||||
setIsCompanyScannerOpen,
|
||||
scanCompanyWebsite,
|
||||
isAiSpecAnalyzerOpen,
|
||||
setIsAiSpecAnalyzerOpen,
|
||||
analyzeSpecPdfWithGemini,
|
||||
aiAnalysisResults,
|
||||
competitorWatchlist,
|
||||
setCompetitorWatchlist,
|
||||
vaultDocuments,
|
||||
setVaultDocuments,
|
||||
reminderSettings,
|
||||
setReminderSettings,
|
||||
webhooksConfig,
|
||||
setWebhooksConfig,
|
||||
apiKeys,
|
||||
setApiKeys,
|
||||
userProfile,
|
||||
notifications,
|
||||
toggleTrackTender,
|
||||
updateKanbanStatus,
|
||||
clearAllFilters
|
||||
}}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useApp = () => useContext(AppContext);
|
||||
Reference in New Issue
Block a user