standardize displayed timestamps to Istanbul
This commit is contained in:
@@ -2242,3 +2242,9 @@ Doğrulama: 294 otomatik test, üretim derlemesi ve browser QA başarılı.
|
|||||||
Kur panelinde TCMB’den alınma zamanı artık tarayıcı yerel saatinden bağımsız olarak İstanbul saatinde gösteriliyor. USD/EUR oranları dört ondalıkla tutarlı sunuluyor ve ekran okuyucular için birim oranı açık adla veriliyor; kaynak bülten tarihi ve stale uyarısı korunuyor.
|
Kur panelinde TCMB’den alınma zamanı artık tarayıcı yerel saatinden bağımsız olarak İstanbul saatinde gösteriliyor. USD/EUR oranları dört ondalıkla tutarlı sunuluyor ve ekran okuyucular için birim oranı açık adla veriliyor; kaynak bülten tarihi ve stale uyarısı korunuyor.
|
||||||
|
|
||||||
Doğrulama: 294 otomatik test, üretim derlemesi ve browser QA başarılı.
|
Doğrulama: 294 otomatik test, üretim derlemesi ve browser QA başarılı.
|
||||||
|
|
||||||
|
### 2026-09-06 · Kullanıcıya gösterilen zamanların İstanbul standardı
|
||||||
|
|
||||||
|
İhale kaynak geçmişi, notlar, AI iş geçmişi, belge deposu, API anahtarları, bildirim abonelikleri ve kur/sağlayıcı kayıtlarındaki ISO zaman gösterimleri `Europe/Istanbul` ile sabitlendi. Kullanıcının cihaz saat dilimi artık aynı kaydın farklı gün/saat görünmesine neden olmuyor.
|
||||||
|
|
||||||
|
Doğrulama: 294 otomatik test, üretim derlemesi ve `git diff --check` başarılı. Farklı cihaz saat dilimlerinde görsel kabul ayrıca yapılmalı.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function AiJobHistory({ikn,onOpen,revision,locked=false,operationFilter='
|
|||||||
{error&&<p role="alert">{error}</p>}
|
{error&&<p role="alert">{error}</p>}
|
||||||
{!jobs.length&&!busy&&!error&&<p>Bu ihale için kayıtlı AI işi yok.</p>}
|
{!jobs.length&&!busy&&!error&&<p>Bu ihale için kayıtlı AI işi yok.</p>}
|
||||||
<ul className="space-y-2 mt-2">{jobs.map(job=><li key={job.id} className="border rounded p-2">
|
<ul className="space-y-2 mt-2">{jobs.map(job=><li key={job.id} className="border rounded p-2">
|
||||||
<p>{operations[job.operation]} · {labels[job.state]} · {new Date(job.createdAt).toLocaleString('tr-TR')}</p>
|
<p>{operations[job.operation]} · {labels[job.state]} · {new Date(job.createdAt).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}</p>
|
||||||
{job.error&&<p>{job.error}</p>}
|
{job.error&&<p>{job.error}</p>}
|
||||||
{job.state==='completed'&&<button type="button" disabled={busy || locked} className="underline py-2 mr-3" onClick={()=>act(async()=>{const data=await getAiJob(job.id);if(alive.current)await onOpen(data.job);})}>Kaynak ve sonucu aç</button>}
|
{job.state==='completed'&&<button type="button" disabled={busy || locked} className="underline py-2 mr-3" onClick={()=>act(async()=>{const data=await getAiJob(job.id);if(alive.current)await onOpen(data.job);})}>Kaynak ve sonucu aç</button>}
|
||||||
{['failed','interrupted'].includes(job.state)&&<button type="button" disabled={busy} className="underline py-2 mr-3" onClick={()=>act(async()=>{await retryAiJob(job.id);await refresh();})}>İşi yeniden başlat</button>}
|
{['failed','interrupted'].includes(job.state)&&<button type="button" disabled={busy} className="underline py-2 mr-3" onClick={()=>act(async()=>{await retryAiJob(job.id);await refresh();})}>İşi yeniden başlat</button>}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function DocumentTextReview({documentId,extractedText,onAnalyze}) {
|
|||||||
<h5 className="font-bold text-sm">Metni karşılaştır ve düzelt</h5>
|
<h5 className="font-bold text-sm">Metni karşılaştır ve düzelt</h5>
|
||||||
<p className="text-xs text-slate-600">Belgenin tüm sayfalarını açıp metin, miktar ve birimleri karşılaştırın. Bu kayıt kullanıcı incelemesidir; resmî onay veya e-imza değildir. Kaydetmek tek başına teklif dosyasını hazır yapmaz.</p>
|
<p className="text-xs text-slate-600">Belgenin tüm sayfalarını açıp metin, miktar ve birimleri karşılaştırın. Bu kayıt kullanıcı incelemesidir; resmî onay veya e-imza değildir. Kaydetmek tek başına teklif dosyasını hazır yapmaz.</p>
|
||||||
{source?.stale && <p role="alert" className="text-xs text-amber-800">Kaynak dosya değişmiş. Önceki inceleme geçerli değil; tüm sayfaları yeniden karşılaştırın.</p>}
|
{source?.stale && <p role="alert" className="text-xs text-amber-800">Kaynak dosya değişmiş. Önceki inceleme geçerli değil; tüm sayfaları yeniden karşılaştırın.</p>}
|
||||||
{source?.review && <p className="text-xs text-slate-500">Son kayıt: sürüm {source.review.revision} · {new Date(source.review.reviewed_at).toLocaleString('tr-TR')}</p>}
|
{source?.review && <p className="text-xs text-slate-500">Son kayıt: sürüm {source.review.revision} · {new Date(source.review.reviewed_at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}</p>}
|
||||||
<label className="block text-xs font-semibold">Kontrol edilen metin
|
<label className="block text-xs font-semibold">Kontrol edilen metin
|
||||||
<textarea value={text} onChange={e=>{setText(e.target.value);setCompared(false);setSaved(null);}} disabled={busy || analyzing}
|
<textarea value={text} onChange={e=>{setText(e.target.value);setCompared(false);setSaved(null);}} disabled={busy || analyzing}
|
||||||
className="block w-full h-64 border rounded p-2 font-mono font-normal mt-1" />
|
className="block w-full h-64 border rounded p-2 font-mono font-normal mt-1" />
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const SummaryCards = () => {
|
|||||||
icon: BellRing,
|
icon: BellRing,
|
||||||
color: 'purple',
|
color: 'purple',
|
||||||
title: oncekiGiris
|
title: oncekiGiris
|
||||||
? `Önceki ziyaretinizden (${new Date(oncekiGiris).toLocaleString('tr-TR')}) sonra yayımlanan ilanlar`
|
? `Önceki ziyaretinizden (${new Date(oncekiGiris).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}) sonra yayımlanan ilanlar`
|
||||||
: 'İlk ziyaretiniz — bugün yayımlanan ilanlar gösteriliyor'
|
: 'İlk ziyaretiniz — bugün yayımlanan ilanlar gösteriliyor'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const TenderDetailModal = () => {
|
|||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
author: userProfile.name,
|
author: userProfile.name,
|
||||||
text: noteText.trim(),
|
text: noteText.trim(),
|
||||||
date: new Date().toLocaleString('tr-TR')
|
date: new Date().toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })
|
||||||
}]});
|
}]});
|
||||||
if (saved) setNoteDrafts(state => acknowledgeNoteDraft(state, noteOwner, noteKey, noteDraft));
|
if (saved) setNoteDrafts(state => acknowledgeNoteDraft(state, noteOwner, noteKey, noteDraft));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function TenderSourceHistory({tender}) {
|
|||||||
{state.error && <div role="alert"><p>{state.error}</p><button type="button" onClick={()=>load(state.loaded?state.nextCursor:null)} disabled={state.loading} className="underline py-2">Yeniden dene</button></div>}
|
{state.error && <div role="alert"><p>{state.error}</p><button type="button" onClick={()=>load(state.loaded?state.nextCursor:null)} disabled={state.loading} className="underline py-2">Yeniden dene</button></div>}
|
||||||
{state.loaded && !state.records.length && <p className="mt-2">Henüz gözlem kaydı yok. Bu, ihale bilgilerinin hiç değişmediğini göstermez.</p>}
|
{state.loaded && !state.records.length && <p className="mt-2">Henüz gözlem kaydı yok. Bu, ihale bilgilerinin hiç değişmediğini göstermez.</p>}
|
||||||
<ol className="space-y-3 mt-3">{state.records.map(record=><li key={record.id} className="border rounded p-2">
|
<ol className="space-y-3 mt-3">{state.records.map(record=><li key={record.id} className="border rounded p-2">
|
||||||
<p>{new Date(record.observedAt).toLocaleString('tr-TR')} · {record.sourceId}</p>
|
<p>{new Date(record.observedAt).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })} · {record.sourceId}</p>
|
||||||
{!record.changes.length?<p>Bu kaynak için ilk gözlem.</p>:record.changes.map(change=><details key={change.field} className="mt-1">
|
{!record.changes.length?<p>Bu kaynak için ilk gözlem.</p>:record.changes.map(change=><details key={change.field} className="mt-1">
|
||||||
<summary className="cursor-pointer">{labels[change.field] || change.field} değişti</summary>
|
<summary className="cursor-pointer">{labels[change.field] || change.field} değişti</summary>
|
||||||
<p className="whitespace-pre-wrap break-words">Önce: {change.before ?? 'Bilinmiyor'}</p>
|
<p className="whitespace-pre-wrap break-words">Önce: {change.before ?? 'Bilinmiyor'}</p>
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ const ApiKeySession = () => {
|
|||||||
<p className="text-[10px] text-slate-400 mt-0.5">
|
<p className="text-[10px] text-slate-400 mt-0.5">
|
||||||
Oluşturma: {new Date(k.olusturma).toLocaleDateString('tr-TR')}
|
Oluşturma: {new Date(k.olusturma).toLocaleDateString('tr-TR')}
|
||||||
{' · '}
|
{' · '}
|
||||||
{k.son_kullanim ? `son kullanım: ${new Date(k.son_kullanim).toLocaleString('tr-TR')}` : 'hiç kullanılmadı'}
|
{k.son_kullanim ? `son kullanım: ${new Date(k.son_kullanim).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}` : 'hiç kullanılmadı'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" onClick={() => iptal(k.id)} disabled={busy || loading}
|
<button type="button" onClick={() => iptal(k.id)} disabled={busy || loading}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function Review({row,weekly,busy,resolve}){
|
|||||||
<p className="font-bold">{row.subject}</p><p>{String(row.to)} · {labels[row.state]}</p><p className="break-all">Kayıt: {row.id}</p>
|
<p className="font-bold">{row.subject}</p><p>{String(row.to)} · {labels[row.state]}</p><p className="break-all">Kayıt: {row.id}</p>
|
||||||
{row.error&&<p>{row.error}</p>}
|
{row.error&&<p>{row.error}</p>}
|
||||||
{row.providerId&&<p className="break-all">Sağlayıcı kaydı: {row.providerId}</p>}
|
{row.providerId&&<p className="break-all">Sağlayıcı kaydı: {row.providerId}</p>}
|
||||||
{row.review&&<p>{row.review.actor} · {new Date(row.review.at).toLocaleString('tr-TR')} · {row.review.note}</p>}
|
{row.review&&<p>{row.review.actor} · {new Date(row.review.at).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })} · {row.review.note}</p>}
|
||||||
{row.state==='uncertain'&&<fieldset disabled={busy} className="space-y-2">
|
{row.state==='uncertain'&&<fieldset disabled={busy} className="space-y-2">
|
||||||
<label className="block">Kontrol açıklaması<textarea className="block border w-full p-2" value={note} onChange={e=>setNote(e.target.value)} minLength={20}/></label>
|
<label className="block">Kontrol açıklaması<textarea className="block border w-full p-2" value={note} onChange={e=>setNote(e.target.value)} minLength={20}/></label>
|
||||||
<label className="block">Sağlayıcı gönderim kimliği<input className="block border w-full p-2" value={providerId} onChange={e=>setProviderId(e.target.value)}/></label>
|
<label className="block">Sağlayıcı gönderim kimliği<input className="block border w-full p-2" value={providerId} onChange={e=>setProviderId(e.target.value)}/></label>
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ const DocumentVaultSession = ({signedIn}) => {
|
|||||||
{d.tur === 'ihale' ? 'İhale dokümanı' : 'Özel firma belgesi'} · {d.kategori || 'Diğer'} · <strong>{formatBayt(d.boyut)}</strong>
|
{d.tur === 'ihale' ? 'İhale dokümanı' : 'Özel firma belgesi'} · {d.kategori || 'Diğer'} · <strong>{formatBayt(d.boyut)}</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-slate-400 mt-0.5">
|
<p className="text-[10px] text-slate-400 mt-0.5">
|
||||||
{new Date(d.olusturma).toLocaleString('tr-TR')}
|
{new Date(d.olusturma).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}
|
||||||
<span className="block">{documentProcessingLabel(d.isleme)}</span>
|
<span className="block">{documentProcessingLabel(d.isleme)}</span>
|
||||||
</p>
|
</p>
|
||||||
<label className="flex items-center gap-1.5 mt-1.5 text-[10px] text-slate-500">
|
<label className="flex items-center gap-1.5 mt-1.5 text-[10px] text-slate-500">
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ const SubscriptionForm = () => {
|
|||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-xs font-bold text-slate-800">{ozet(a)}</p>
|
<p className="text-xs font-bold text-slate-800">{ozet(a)}</p>
|
||||||
<p className="text-[10px] text-slate-400 mt-0.5">
|
<p className="text-[10px] text-slate-400 mt-0.5">
|
||||||
{a.sonGonderim ? `son gönderim: ${new Date(a.sonGonderim).toLocaleString('tr-TR')}` : 'ilk tur bekleniyor'}
|
{a.sonGonderim ? `son gönderim: ${new Date(a.sonGonderim).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}` : 'ilk tur bekleniyor'}
|
||||||
{a.deliveryStatus && <span className="block text-amber-700">{a.deliveryStatus.state==='uncertain'?'Gönderim sonucu belirsiz. Uygulama yöneticisinin sağlayıcı kaydını kontrol etmesi gerekiyor.':'E-posta gönderimi bekliyor.'}</span>}
|
{a.deliveryStatus && <span className="block text-amber-700">{a.deliveryStatus.state==='uncertain'?'Gönderim sonucu belirsiz. Uygulama yöneticisinin sağlayıcı kaydını kontrol etmesi gerekiyor.':'E-posta gönderimi bekliyor.'}</span>}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -25,5 +25,5 @@ export function analysisView(data,input) {
|
|||||||
return {...data.result,tenderKey:input.tender?.ikn || input.tender?.id || null,
|
return {...data.result,tenderKey:input.tender?.ikn || input.tender?.id || null,
|
||||||
mode:data.mode || (input.mode==='derin'?'derin':'standart'),fileName:input.fileName,
|
mode:data.mode || (input.mode==='derin'?'derin':'standart'),fileName:input.fileName,
|
||||||
model:`${data.model.label} (${data.model.provider})`,modelId:data.model.model,
|
model:`${data.model.label} (${data.model.provider})`,modelId:data.model.model,
|
||||||
attempts:data.attempts,analyzedAt:new Date(data.analyzedAt).toLocaleString('tr-TR')};
|
attempts:data.attempts,analyzedAt:new Date(data.analyzedAt).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export function companyScanProposal(data) {
|
|||||||
if(!Array.isArray(result[key])||result[key].some(value=>typeof value!=='string'||!value.trim()))throw new Error('Tarama önerileri eksik veya geçersiz. Profil değiştirilmedi.');
|
if(!Array.isArray(result[key])||result[key].some(value=>typeof value!=='string'||!value.trim()))throw new Error('Tarama önerileri eksik veya geçersiz. Profil değiştirilmedi.');
|
||||||
return [...new Set(result[key].map(value=>value.trim()))];
|
return [...new Set(result[key].map(value=>value.trim()))];
|
||||||
};
|
};
|
||||||
return {scrapedAt:new Date().toLocaleString('tr-TR'),extractedSectors:array('sektorler'),extractedProducts:array('urunler'),
|
return {scrapedAt:new Date().toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }),extractedSectors:array('sektorler'),extractedProducts:array('urunler'),
|
||||||
extractedCertifications:array('sertifikalar'),ihaleKonulari:array('ihaleKonulari'),ozet:typeof result.ozet==='string'?result.ozet:null,
|
extractedCertifications:array('sertifikalar'),ihaleKonulari:array('ihaleKonulari'),ozet:typeof result.ozet==='string'?result.ozet:null,
|
||||||
tarananSayfa:data.tarananSayfa,gercekTarama:true,matchScoreBoost:0};
|
tarananSayfa:data.tarananSayfa,gercekTarama:true,matchScoreBoost:0};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export const fetchLiveEKAPData = async () => {
|
|||||||
resolve({
|
resolve({
|
||||||
success: true,
|
success: true,
|
||||||
source: 'EKAP Open Data Live Stream API v2.6',
|
source: 'EKAP Open Data Live Stream API v2.6',
|
||||||
fetchedAt: new Date().toLocaleString('tr-TR'),
|
fetchedAt: new Date().toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }),
|
||||||
tenders: REAL_EKAP_LIVE_TENDERS,
|
tenders: REAL_EKAP_LIVE_TENDERS,
|
||||||
totalActiveLiveCount: 58920,
|
totalActiveLiveCount: 58920,
|
||||||
todayAddedCount: 1445,
|
todayAddedCount: 1445,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function addLocalNotification(current,title,text) {
|
|||||||
export function mergeServerNotifications(current,rows) {
|
export function mergeServerNotifications(current,rows) {
|
||||||
if(!Array.isArray(rows)||rows.some(row=>typeof row?.id!=='string'||typeof row.baslik!=='string'||typeof row.metin!=='string'))throw new Error('Bildirim listesi geçersiz.');
|
if(!Array.isArray(rows)||rows.some(row=>typeof row?.id!=='string'||typeof row.baslik!=='string'||typeof row.metin!=='string'))throw new Error('Bildirim listesi geçersiz.');
|
||||||
const remote=rows.map(row=>({id:'server:'+row.id,serverId:row.id,title:row.baslik,text:row.metin,
|
const remote=rows.map(row=>({id:'server:'+row.id,serverId:row.id,title:row.baslik,text:row.metin,
|
||||||
time:new Date(row.zaman).toLocaleString('tr-TR'),read:!!row.okundu,ikn:row.ikn}));
|
time:new Date(row.zaman).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }),read:!!row.okundu,ikn:row.ikn}));
|
||||||
return keepRecentLocal([...remote,...current.filter(row=>!row.serverId)]);
|
return keepRecentLocal([...remote,...current.filter(row=>!row.serverId)]);
|
||||||
}
|
}
|
||||||
export async function acknowledgeNotifications(snapshot,request) {
|
export async function acknowledgeNotifications(snapshot,request) {
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ export const parseWebsiteCapabilitiesDynamically = (url) => {
|
|||||||
const isMatched = matchRule.keywords.some(kw => cleanUrl.includes(kw));
|
const isMatched = matchRule.keywords.some(kw => cleanUrl.includes(kw));
|
||||||
if (isMatched) {
|
if (isMatched) {
|
||||||
return {
|
return {
|
||||||
scrapedAt: new Date().toLocaleString('tr-TR'),
|
scrapedAt: new Date().toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }),
|
||||||
extractedSectors: [matchRule.sector],
|
extractedSectors: [matchRule.sector],
|
||||||
extractedProducts: [...matchRule.products],
|
extractedProducts: [...matchRule.products],
|
||||||
extractedCertifications: [...matchRule.certifications],
|
extractedCertifications: [...matchRule.certifications],
|
||||||
@@ -179,7 +179,7 @@ export const parseWebsiteCapabilitiesDynamically = (url) => {
|
|||||||
// 2. Generic Pure Extraction (Guarantees ZERO wrong cross-sector hallucination)
|
// 2. Generic Pure Extraction (Guarantees ZERO wrong cross-sector hallucination)
|
||||||
const formattedBrandName = coreDomainName.toUpperCase();
|
const formattedBrandName = coreDomainName.toUpperCase();
|
||||||
return {
|
return {
|
||||||
scrapedAt: new Date().toLocaleString('tr-TR'),
|
scrapedAt: new Date().toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' }),
|
||||||
extractedSectors: [`${formattedBrandName} Uzmanlık Alanı`],
|
extractedSectors: [`${formattedBrandName} Uzmanlık Alanı`],
|
||||||
extractedProducts: [
|
extractedProducts: [
|
||||||
`${formattedBrandName} Kurumsal Ürün Serisi`,
|
`${formattedBrandName} Kurumsal Ürün Serisi`,
|
||||||
|
|||||||
Reference in New Issue
Block a user