Kurumların kritik sistem ve hizmetlerinde yaşanabilecek kesintilere karşı alternatif çalışma yöntemlerini, sorumluları ve müdahale adımlarını belirleyen bir iş sürekliliği aracıdır.
Temel amacı; hizmet kaybını en aza indirmek, operasyonların devamlılığını sağlamak ve normal işleyişe hızlı, güvenli ve kontrollü dönüşü desteklemektir.
Bir Not defteri açarak, aşağıdaki (Mavi satırlar) html kodu içine kaydedin. Not defteri uzantısını .txt yerine ".html" olarak değiştirip kaydedin. Ör: "Kritik Hizmet Kesintisi Alternatif İşleyiş Planı.html" sayfayı kapatıp tekrar açtığınızda, web browser üzerinden sayfanız açılacaktır.
Sol tarafta görünen logo için, html dosyanız nerede ise, masaüstü veya oluşturulan bir klasör. Logonuzda aynı yerde olursa forma yansır. Logo ismi "logo.jpg" olmalı. Çift tıklayarak açtığınızda, browser üzerinde form açılışı gerçekleşir. Veya alanına 5 kez peş peşe tıklayarak çalışma esnasında logo değişimi de yapılabilir.
Başlık, başlığın altındaki, başlığın sağındaki ve alttaki yazıları, not defteri içinde "Ctrl + F" tuşları ile kelime yazarak bulabilir, ilgili metinleri kendinize göre uyarlayarak kaydederek kullanabilirsiniz. Başlık alanındaki "OLGUN BAŞKANLIĞI" alanına 5 kez peş peşe tıklayarak; Departman, Müdürlük veya Başkanlığınıza ait bilgiyi manuel girebilirsiniz.
Formun boş hali;
Formun dolu hali:
PDF / Yazdır
Dışarı Aktarılan Excel Verisi:
HTML Kod:
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>Kritik Hizmet Kesintisi Alternatif İşleyiş Planı</title>
<style>
/* OLGUN UI2 — Tek, tutarlı tasarım kaynağı: beyaz başlık ve BUTON paneli */
:root{
--bg:#f8fafc; --canvas:#eaf0f6; --white:#ffffff;
--blue:#0284c7; --blue-deep:#075985; --blue-soft:#e7f3fc;
--line:#cbd5e1; --frame:#94a3b8; --text:#1e293b; --muted:#64748b;
--shadow:0 2px 8px rgba(15,23,42,.07);
}
*,*::before,*::after{box-sizing:border-box}
html{font-family:"Segoe UI",Arial,sans-serif;width:100%;min-height:100%;background:var(--canvas)}
body{margin:0;width:100%;min-height:100vh;background:var(--canvas);font-size:13px;line-height:1.45;color:var(--text)}
button,input,select,textarea{font:inherit}button{cursor:pointer}
/* Olgun UI2 ana dış çerçevesi: 10px dış aralık ile dört kenarı görünür, max-width YOK. */
.shell{
display:block;width:calc(100% - 20px);max-width:none;
min-height:calc(100vh - 20px);min-height:calc(100dvh - 20px);
margin:10px auto;padding:16px 17px 18px;background:var(--bg);
border:2px solid #94a3b8;border-top:6px solid var(--blue);
border-radius:6px;box-shadow:0 3px 16px rgba(15,23,42,.12),inset 0 1px 0 #fff;
}
.panel{background:var(--white);border:1px solid var(--line);border-radius:6px;box-shadow:var(--shadow)}
/* BAŞLIK: çerçevesi ve zemini araç çubuğundan bağımsız beyaz panel */
.header.panel{
display:grid;grid-template-columns:72px minmax(0,1fr) minmax(180px,34%);
align-items:center;gap:16px;min-height:102px;padding:13px 16px;
margin:0 0 12px;background:#fff;border:1px solid var(--line);
border-radius:6px;box-shadow:var(--shadow);
}
.logo{
position:relative;display:grid;place-items:center;width:72px;height:72px;min-width:0;
padding:4px;overflow:hidden;border:1px dashed #94a3b8;border-radius:5px;
background:#f8fafc;color:#56718b;font-size:11px;font-weight:700;
cursor:pointer;touch-action:manipulation;
}
.logo img{display:none;width:100%;height:100%;object-fit:contain}
.logo.has-logo{background:#fff;border-style:solid}.logo.has-logo img{display:block}
.logo.has-logo .logo-placeholder{display:none}
.logo-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;pointer-events:none;line-height:1.2;color:#56718b;font-weight:700}
.logo-placeholder small{font-size:9px;font-weight:500;color:#64748b}.logo:hover{border-color:var(--blue)}
.logo:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.brand-titles{min-width:0}.brand-titles h1{margin:0 0 5px;font-size:19px;line-height:1.3;color:#153e59;font-weight:700}
.brand-titles p{margin:0;font-size:12px;line-height:1.4;color:var(--muted)}
.org{
justify-self:end;width:max-content;max-width:100%;min-width:0;
text-align:right;white-space:normal;overflow-wrap:anywhere;
font-size:17px;line-height:1.33;color:#000;font-weight:700;
user-select:none;-webkit-user-select:none;outline:0;
}
.org[contenteditable="true"]{outline:2px dashed var(--blue);border-radius:4px;padding:4px 7px;background:#f0f9ff;user-select:text;-webkit-user-select:text}
/* ARAÇ ÇUBUĞU: bağımsız tam BEYAZ zemin, tek nötr çerçeve, mavi şerit YOK */
.toolbar.panel{
display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;
gap:9px;min-height:62px;margin:0 0 13px;padding:10px 12px;
background:#ffffff;border:1px solid #cbd5e1;border-radius:6px;
box-shadow:0 2px 7px rgba(15,23,42,.06);
}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:9px 14px;border:1px solid #cbd5e1;border-radius:5px;font-size:12px;font-weight:700;line-height:1.25;white-space:nowrap}
.btn:focus-visible,.input:focus-visible,.org:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.toolbar .btn.import{background:#dbeafe;color:#075985;border-color:#bfdbfe}.toolbar .btn.import:hover{background:#c7e0fd}
.toolbar .btn.export{background:#dcfce7;color:#166534;border-color:#9fdbb5}.toolbar .btn.export:hover{background:#c7f4d6}
.toolbar .btn.print{background:#f1f5f9;color:#334155;border-color:#cbd5e1}.toolbar .btn.print:hover{background:#e2e8f0}
/* Ölçümler ve beyaz içerik panelleri */
.metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 13px}
.metric{min-height:64px;padding:10px 13px}.metric-label{font-size:11px;color:#64748b;font-weight:600}
.metric-value{margin-top:3px;font-size:16px;color:#17415a;font-weight:750;overflow-wrap:anywhere}
.metric.good .metric-value{color:#166534}.metric.bad .metric-value{color:#b91c1c}.metric.warn .metric-value{color:#a16207}
.section.panel{margin:0 0 12px;background:#fff;border:1px solid var(--line);break-inside:auto}
.section-title{margin:0;padding:9px 13px;background:var(--blue-soft);border-left:4px solid var(--blue);border-bottom:1px solid #d1e0eb;font-size:13px;font-weight:700;color:#134e72}
.section-body{padding:13px 15px 15px}
.fields{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px 14px}
.field{min-width:0;break-inside:avoid}.field.w2{grid-column:span 2}.field.w4{grid-column:1/-1}
.field label{display:block;margin:0 0 5px;color:#334155;font-size:11.5px;font-weight:650}
.input{display:block;width:100%;min-height:35px;padding:7px 9px;outline:0;border:1px solid #cbd5e1;border-radius:5px;background:#fff;color:#17293a}
.input:focus{border-color:var(--blue);box-shadow:0 0 0 2px #0284c71c}
.input[readonly]{background:#f1f5f9;color:#475569}.input::placeholder{color:#a6b1bf}
textarea.input{min-height:67px;resize:vertical;overflow:hidden;line-height:1.5}
.hint{font-size:11px;color:#64748b;margin:7px 0 0}
.checks{display:flex;align-items:center;flex-wrap:wrap;gap:10px 20px;padding:10px 11px;background:#f8fafc;border:1px solid #e0e8f1;border-radius:5px}
.check-label{display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:600;color:#334155;cursor:pointer}.check-label input{width:15px;height:15px;accent-color:var(--blue)}
.table-head{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;margin:1px 0 9px}
.table-head p{color:#64748b;font-size:11px;margin:0}.add{border:1px solid #9ecbed;color:#075985;background:#e6f5ff;font-weight:700;padding:6px 10px;border-radius:4px}.add:hover{background:#d4ecfe}
.table-wrap{overflow-x:auto;border:1px solid #e0e7ef;border-radius:5px}.table{width:100%;min-width:690px;border-collapse:collapse;table-layout:fixed}
.table th{padding:8px 7px;background:#f1f6fa;font-size:11px;font-weight:650;text-align:left;color:#38556c;border-bottom:1px solid #dce5ed}
.table td{padding:6px;border-bottom:1px solid #e7edf3;vertical-align:top}.table tbody tr:last-child td{border-bottom:0}
.table .input{min-height:32px;padding:6px;font-size:12px}.table textarea.input{min-height:40px}
.table .count{width:36px;text-align:center;color:#64748b;font-size:12px}.table .remove{width:43px;text-align:center}
.delete{border:1px solid #fecaca;background:#fff7f7;border-radius:4px;color:#be123c;font-size:15px;padding:1px 8px}.delete:hover{background:#ffe4e6}
.footer{font-size:11px;color:#64748b;padding:10px 2px 3px;display:flex;flex-wrap:wrap;gap:8px 20px;align-items:center;justify-content:space-between;border-top:1px solid #cbd5e1;margin-top:10px}
.toast{position:fixed;bottom:20px;right:20px;z-index:10;background:#143d56;color:#fff;padding:10px 14px;border-radius:6px;box-shadow:0 6px 26px #0f172a29;max-width:min(400px,calc(100vw - 30px));display:none}
.toast.show{display:block}.print-value{display:none}
@media(max-width:1000px){
.fields{grid-template-columns:repeat(2,minmax(0,1fr))}.field.w2{grid-column:span 2}
.header.panel{grid-template-columns:60px minmax(0,1fr) minmax(135px,32%)}
.logo{width:60px;height:60px}.brand-titles h1{font-size:17px}.org{font-size:16px}
}
@media(max-width:620px){
.shell{width:calc(100% - 12px);min-height:calc(100dvh - 12px);margin:6px auto;padding:10px;border-width:2px;border-top-width:5px;border-radius:5px}
.header.panel{grid-template-columns:52px minmax(0,1fr);gap:9px;padding:11px;min-height:0;margin-bottom:10px}
.logo{width:52px;height:52px}.brand-titles h1{font-size:14px}.brand-titles p{font-size:10px}
.org{grid-column:1/-1;justify-self:end;font-size:15px}
.toolbar.panel{min-height:0;padding:9px 8px;gap:7px;margin-bottom:10px}
.btn{padding:8px 9px;font-size:11px}.metrics{grid-template-columns:repeat(2,minmax(0,1fr))}
.metric{padding:8px}.fields{grid-template-columns:1fr}.field.w2,.field.w4{grid-column:1}
.section-body{padding:11px}.section-title{padding:8px 10px}
}
/* Sayfa numaraları @page kenar kutularında; boş sayfa için zorunlu kırılma yok. */
@page{
size:A4 landscape;
margin:8mm 10mm 13mm 10mm;
@bottom-left{
content:"KRİTİK HİZMET KESİNTİSİ • ALTERNATİF İŞLEYİŞ PLANI";
font:600 7.5pt "Segoe UI",Arial,sans-serif;
color:#64748b;
vertical-align:middle;
border-top:.5pt solid #cbd5e1;
padding-top:2mm;
}
@bottom-right{
content:"Sayfa " counter(page) " / " counter(pages);
font:700 8.5pt "Segoe UI",Arial,sans-serif;
color:#075985;
text-align:right;
vertical-align:middle;
border-top:.5pt solid #cbd5e1;
padding-top:2mm;
}
}
@media print{
/* Baskıda içeriği okunabilir tutarak fazladan, çok az içerikli son sayfayı önle. */
/* Ön yüzde güçlendirilen çerçeveler baskı sayfasını şişirmesin. */
.header.panel{padding:7px 9px!important;margin:0 0 2mm!important;box-shadow:none!important;border:1px solid #bbd0df!important;border-left:4px solid #0284c7!important}
.section.panel{box-shadow:none!important;border-color:#cbd5e1!important}
.toolbar.panel{display:none!important}
.shell{zoom:.98;margin:0!important;width:100%!important;min-height:0!important;border-radius:0!important}
*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}html,body{font-size:9pt;color:#000;background:#fff;min-height:0!important;height:auto!important;overflow:visible!important}
.shell{width:100%;max-width:none;min-height:0!important;height:auto!important;margin:0!important;padding:0 1mm 0;background:#fff;border:1px solid #bfccd7;border-top:3px solid #0284c7;box-shadow:none;overflow:visible;break-after:auto;page-break-after:auto}.panel{box-shadow:none;border-color:#cbd5e1}.header{grid-template-columns:55px minmax(0,1fr) minmax(130px,35%);min-height:0;padding:7px 9px;background:#f2f9ff!important;margin-bottom:2mm;break-after:avoid}.logo{width:50px;height:50px;border-style:solid!important}.logo-placeholder small{display:none}.brand-titles h1{font-size:14pt}.brand-titles p{font-size:9pt}.org{grid-column:auto;font-size:14pt;justify-self:end}.toolbar,.toast,.footer,.table-head .add,.table .remove{display:none!important}.metrics{grid-template-columns:repeat(4,minmax(0,1fr));gap:2mm;margin-bottom:2mm}.metric{min-height:0;padding:5px 8px}.metric-label{font-size:7pt}.metric-value{font-size:10pt}.section{margin:0 0 1.8mm;break-inside:auto;page-break-before:auto;page-break-after:auto}.section-title{font-size:9pt;padding:5px 8px;background:#edf7fd!important;break-after:avoid}.section-body{padding:5px 8px}.fields{grid-template-columns:repeat(4,minmax(0,1fr));gap:4px 8px}.field.w2{grid-column:span 2}.field.w4{grid-column:1/-1}.field label{font-size:7.5pt;margin-bottom:1px}.field .input,.table .input{display:none!important}.print-value{display:block;border:1px solid #e0e7ef;padding:2px 4px;border-radius:3px;min-height:15px;white-space:pre-wrap;overflow-wrap:anywhere;font-size:8pt;line-height:1.3}.print-value:empty::after{content:' ';white-space:pre}.table-wrap{overflow:visible}.table{min-width:0;font-size:7pt}.table th{font-size:7pt;padding:3px;border:1px solid #d7e1ea}.table td{padding:1px;border:1px solid #d7e1ea}.table .print-value{border:0;padding:1px 2px;font-size:7pt;min-height:10px}.table{page-break-after:auto}.table thead{display:table-header-group}.table tr{break-inside:avoid;page-break-inside:avoid}.table-head{margin-bottom:3px;break-after:avoid}.table-head p{font-size:8pt}.checks{padding:6px 8px;background:#f8fafc!important;border:1px solid #e0e7ef;break-inside:avoid}.check-label{font-size:8pt}
}
</style>
</head>
<body>
<main class="shell">
<header class="header panel">
<button type="button" class="logo" id="logoBox" aria-label="Kurum logosu. Logo yoksa tıklayarak yükleyin; mevcut logoyu değiştirmek için 5 kez tıklayın." title="Logo yoksa yüklemek için tıklayın; mevcut logoyu değiştirmek için 5 kez tıklayın."><img id="logo" src="logo.jpg" alt="Kurum logosu"><span id="logoPlaceholder" class="logo-placeholder">LOGO<small>Yükle</small></span></button><input id="logoFileInput" type="file" accept="image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif" hidden>
<div class="brand-titles"><h1>Kritik Hizmet Kesintisi Alternatif İşleyiş Planı</h1><p>(Kritik hizmetlerin kesintide sürdürülmesi ve normal işleyişe kontrollü dönüşü)</p></div>
<div id="org" class="org" role="textbox" aria-label="Kurum adı; düzenlemek için 5 kez tıklayın veya Enter tuşuna 5 kez basın" aria-readonly="true" tabindex="0" title="Kurum adını değiştirmek için art arda 5 kez tıklayın ya da dokunun">OLGUN BAŞKANLIĞI</div>
</header>
<nav class="toolbar panel" aria-label="Form araçları">
<button type="button" id="importBtn" class="btn import">↓ Excel İçeri Aktar</button>
<button type="button" id="exportBtn" class="btn export">↑ Excel Dışarı Aktar</button>
<button type="button" id="printBtn" class="btn print">▣ PDF / Yazdır</button>
<input id="fileInput" type="file" accept=".xlsx,.xls" hidden>
</nav>
<div class="metrics" aria-live="polite">
<div class="metric panel" id="metricElapsed"><div class="metric-label">Kesinti Süresi</div><div class="metric-value" id="statDuration">—</div></div>
<div class="metric panel" id="metricRto"><div class="metric-label">RTO Takibi</div><div class="metric-value" id="statRto">—</div></div>
<div class="metric panel" id="metricRpo"><div class="metric-label">RPO Takibi</div><div class="metric-value" id="statRpo">—</div></div>
<div class="metric panel" id="metricSteps"><div class="metric-label">Tamamlanan Adımlar</div><div class="metric-value" id="statSteps">0 / 0</div></div>
</div>
<div id="formSections"></div>
<div class="footer"><span>Form tek bir plan kaydı içindir. Veriler tarayıcıda otomatik saklanmaz; çevrimdışı kaydetmek ve yeniden açmak için Excel işlemlerini kullanın. Yazıcınız destekliyorsa önlü arkalı baskıyı yazdırma ekranından seçebilirsiniz.</span></div>
</main>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script>
'use strict';
/* Tek dosyada, internet baglantisi olmadan XLSX (ZIP + SpreadsheetML) desteği. */
const XlsxLocal=(()=>{
const enc=new TextEncoder(),dec=new TextDecoder();
const esc=s=>String(s??'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
const letters=index=>{let n=index+1,s='';while(n){n--;s=String.fromCharCode(65+n%26)+s;n=Math.floor(n/26)}return s};
const crcTable=new Uint32Array(256);for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=c&1?0xedb88320^(c>>>1):c>>>1;crcTable[n]=c>>>0}
const crc=u8=>{let c=0xffffffff;for(const b of u8)c=crcTable[(c^b)&255]^(c>>>8);return(c^0xffffffff)>>>0};
const u16=(v,o,n)=>v.setUint16(o,n,true),u32=(v,o,n)=>v.setUint32(o,n>>>0,true);
function zip(files){const chunks=[],central=[];let offset=0;
for(const [name,content] of Object.entries(files)){
const path=enc.encode(name),data=enc.encode(content),check=crc(data),header=new Uint8Array(30+path.length),view=new DataView(header.buffer);
u32(view,0,0x04034b50);u16(view,4,20);u16(view,6,0x0800);u16(view,8,0);u32(view,14,check);u32(view,18,data.length);u32(view,22,data.length);u16(view,26,path.length);header.set(path,30);
chunks.push(header,data);const cent=new Uint8Array(46+path.length),cv=new DataView(cent.buffer);
u32(cv,0,0x02014b50);u16(cv,4,20);u16(cv,6,20);u16(cv,8,0x0800);u16(cv,10,0);u32(cv,16,check);u32(cv,20,data.length);u32(cv,24,data.length);u16(cv,28,path.length);u32(cv,42,offset);cent.set(path,46);central.push(cent);offset+=header.length+data.length;
}
const size=central.reduce((s,a)=>s+a.length,0),end=new Uint8Array(22),e=new DataView(end.buffer);u32(e,0,0x06054b50);u16(e,8,central.length);u16(e,10,central.length);u32(e,12,size);u32(e,16,offset);
return new Blob([...chunks,...central,end],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
}
const declaration='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
function sheetXml(rows){let xml=declaration+'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>';
rows.forEach((row,i)=>{xml+='<row r="'+(i+1)+'">';row.forEach((value,j)=>{if(value===null||value===undefined||value==='')return;const ref=letters(j)+(i+1);
if(typeof value==='number'&&Number.isFinite(value))xml+='<c r="'+ref+'"><v>'+value+'</v></c>';
else xml+='<c r="'+ref+'" t="inlineStr"><is><t xml:space="preserve">'+esc(value)+'</t></is></c>';
});xml+='</row>'});return xml+'</sheetData></worksheet>';
}
function write(sheets){const names=Object.keys(sheets),files={};
files['[Content_Types].xml']=declaration+'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'+names.map((n,i)=>'<Override PartName="/xl/worksheets/sheet'+(i+1)+'.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>').join('')+'</Types>';
files['_rels/.rels']=declaration+'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>';
files['xl/workbook.xml']=declaration+'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>'+names.map((n,i)=>'<sheet name="'+esc(n)+'" sheetId="'+(i+1)+'" r:id="rId'+(i+1)+'"/>').join('')+'</sheets></workbook>';
files['xl/_rels/workbook.xml.rels']=declaration+'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+names.map((n,i)=>'<Relationship Id="rId'+(i+1)+'" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet'+(i+1)+'.xml"/>').join('')+'</Relationships>';
names.forEach((name,i)=>files['xl/worksheets/sheet'+(i+1)+'.xml']=sheetXml(sheets[name]));return zip(files);
}
function parseXml(data){const doc=new DOMParser().parseFromString(data,'application/xml');if(doc.getElementsByTagName('parsererror').length)throw Error('Excel XML içeriği okunamadı.');return doc}
const elems=(el,name)=>Array.from(el.getElementsByTagNameNS('*',name));
async function unzip(file){const bytes=new Uint8Array(await file.arrayBuffer()),v=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength);let end=-1;
for(let i=bytes.length-22;i>=Math.max(0,bytes.length-65557);i--){if(v.getUint32(i,true)===0x06054b50){end=i;break}}
if(end<0)throw Error('Geçerli XLSX dosyası değil.');const count=v.getUint16(end+10,true);let pos=v.getUint32(end+16,true),entries={};
for(let i=0;i<count;i++){if(v.getUint32(pos,true)!==0x02014b50)throw Error('Excel ZIP dizini bozuk.');
const method=v.getUint16(pos+10,true),size=v.getUint32(pos+20,true),len=v.getUint16(pos+28,true),extra=v.getUint16(pos+30,true),comment=v.getUint16(pos+32,true),loc=v.getUint32(pos+42,true);
const name=dec.decode(bytes.subarray(pos+46,pos+46+len));if(v.getUint32(loc,true)!==0x04034b50)throw Error('Excel ZIP bölümü bozuk.');
const dataStart=loc+30+v.getUint16(loc+26,true)+v.getUint16(loc+28,true),compressed=bytes.subarray(dataStart,dataStart+size);let result;
if(method===0)result=compressed;
else if(method===8){if(typeof DecompressionStream==='undefined')throw Error('Bu tarayıcı sıkıştırılmış Excel dosyasını açamıyor. Chrome veya Edge kullanın.');
result=new Uint8Array(await new Response(new Blob([compressed]).stream().pipeThrough(new DecompressionStream('deflate-raw'))).arrayBuffer());
}else throw Error('Desteklenmeyen Excel sıkıştırması.');entries[name.replace(/^\//,'')]=dec.decode(result);pos+=46+len+extra+comment;
}
return entries;
}
function parseSheet(xml,shared){const doc=parseXml(xml),out=[];
for(const row of elems(doc,'row')){const index=Number(row.getAttribute('r'))-1;const arr=[];for(const cell of elems(row,'c')){
const ref=(cell.getAttribute('r')||'A').match(/^[A-Z]+/)?.[0]||'A';let col=0;for(const ch of ref)col=col*26+ch.charCodeAt(0)-64;col--;
const type=cell.getAttribute('t');let value='';if(type==='inlineStr')value=elems(cell,'t').map(t=>t.textContent||'').join('');
else{const v=elems(cell,'v')[0]?.textContent||'';value=type==='s'?(shared[Number(v)]||''):v}
arr[col]=value;
}out[index>=0?index:out.length]=arr;
}return out.map(x=>x||[]);
}
async function read(file){const files=await unzip(file),wb=parseXml(files['xl/workbook.xml']||''),rels=parseXml(files['xl/_rels/workbook.xml.rels']||'');const links={};
for(const rel of elems(rels,'Relationship'))links[rel.getAttribute('Id')]=rel.getAttribute('Target');
const shared=files['xl/sharedStrings.xml']?elems(parseXml(files['xl/sharedStrings.xml']),'si').map(si=>elems(si,'t').map(t=>t.textContent||'').join('')):[];
const sheets={};for(const s of elems(wb,'sheet')){const name=s.getAttribute('name'),rid=s.getAttributeNS('http://schemas.openxmlformats.org/officeDocument/2006/relationships','id')||s.getAttribute('r:id');let target=links[rid];if(!target)continue;
target=target[0]==='/'?target.slice(1):target.startsWith('xl/')?target:'xl/'+target.replace(/^\.\//,'');target=target.replace(/\/[^/]+\/\.\.\//g,'/');if(!files[target])continue;sheets[name]=parseSheet(files[target],shared);
}return sheets;
}
function download(blob,filename){const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=filename;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),30000)}
return {write,read,download};
})();
const F=(key,label,type='text',width=1,opts='')=>({key,label,type,width,opts});
const sections=[
{title:'Plan ve Kritik Hizmet',fields:[
F('planNo','Plan No'),F('serviceName','Kritik Hizmetin Adı'),F('serviceDept','Sorumlu Birim'),F('planOwner','Plan / Hizmet Sorumlusu'),
F('criticality','Kritiklik Seviyesi','select',1,'Çok Kritik|Kritik|Orta|Düşük'),F('planStatus','Plan Durumu','select',1,'Taslak|Onay Bekliyor|Onaylandı|Aktif|Kapatıldı'),F('version','Plan Sürümü'),F('prepDate','Plan Tarihi','date')
]},
{title:'Kesinti ve Kurtarma Hedefleri',fields:[
F('outageType','Kesinti Türü','select',1,'Siber Saldırı|Donanım Arızası|Yazılım Hatası|Ağ / İletişim|Enerji|Tedarikçi|Doğal Afet|Diğer'),F('outageStart','Kesinti Başlangıcı','datetime-local'),F('serviceResumedAt','Asgari Hizmetin Yeniden Başladığı An','datetime-local'),F('outageDuration','Kesinti Süresi (otomatik)','readonly'),
F('rtoMins','RTO (dakika)','number'),F('rpoMins','RPO (dakika)','number'),F('actualLossMins','Gerçekleşen Veri Kaybı (dakika)','number'),F('minCapacity','Asgari Hizmet Kapasitesi (%)','percent'),
F('scenario','Kesintinin Kapsamı / İşe Etkisi','textarea',4)
]},
{title:'Alternatif İşleyiş ve Devreye Alma',fields:[
F('altType','Alternatif İşleyiş Yöntemi','select',1,'Manuel İşlem|Yedek Sistem|İkincil Lokasyon|Geçici Uygulama|Tedarikçi Desteği|Karma Yöntem|Diğer'),F('decisionMaker','Devreye Alma Yetkilisi'),F('activatedAt','Devreye Alınma Tarihi / Saati','datetime-local'),F('actualCapacity','Sağlanan Kapasite (%)','percent'),
F('altDesc','Alternatif Yöntemin Açıklaması','textarea',2),F('launchConditions','Başlatma Şartları ve Kısıtlar','textarea',2),
F('altTools','Gerekli Kaynaklar / Bağımlılıklar','textarea',2),F('accessRules','Veri ve Erişim Güvenliği Önlemleri','textarea',2)
]},
{title:'Uygulama Adımları',table:'steps'},
{title:'Sorumlular ve Bilgilendirme',fields:[F('internalAnnouncement','İç / Dış Paydaşlara Bildirim Yöntemi ve Sıklığı','textarea',4)],table:'people'},
{title:'Normal İşleyişe Geri Dönüş',fields:[
F('returnPrereqs','Normal İşleyişe Dönüş Koşulları','textarea',2),F('dataSync','Geçici Kayıtların Ana Sisteme Aktarımı','textarea',2),
F('returnIntegrity','Veri Bütünlüğü / Mükerrer İşlem Kontrolü','textarea',2),F('closureNotes','Kalan Sorunlar / Kapanış Notu','textarea',2),
F('actualReturn','Normal İşleyişe Dönüş Tarihi / Saati','datetime-local'),F('returnApproval','Geri Dönüşü Onaylayan'),F('altClosure','Alternatif İşleyiş Durumu','select',1,'Bekliyor|Devam Ediyor|Sonlandırıldı'),F('returnState','Geri Dönüş Kontrolü','select',1,'Kontrol Edilmedi|Kısmen Doğrulandı|Doğrulandı')
]},
{title:'Test, Onay ve İyileştirme',fields:[
F('drillDate','Son Test / Tatbikat Tarihi','date'),F('drillResult','Test Sonucu','select',1,'Başarılı|Kısmen Başarılı|Başarısız|Test Edilmedi'),F('approver','Planı Onaylayan'),F('approvalDate','Onay Tarihi','date'),
F('lessons','Eksiklikler, Önlemler ve Öğrenilmiş Dersler','textarea',2),F('nextReview','Sonraki Gözden Geçirme Tarihi','date')
]}
];
const tables={
steps:{title:'Uygulama adımları',sheet:'Uygulama Adımları',columns:[F('activity','İşlem / Faaliyet'),F('owner','Sorumlu Personel'),F('targetMin','Hedef Süre (dk)','number'),F('state','İşlem Durumu','select',1,'Bekliyor|Devam Ediyor|Tamamlandı|Başarısız'),F('notes','Açıklama')]},
people:{title:'Sorumlu ve yedek personel',sheet:'Personel',columns:[F('name','Adı ve Soyadı'),F('department','Görevi / Departmanı'),F('contact','İletişim Bilgisi'),F('deputy','Alternatif Sorumlu'),F('task','Görev ve Yetkileri')]}
};
const allFields=sections.flatMap(s=>s.fields||[]);
const $=id=>document.getElementById(id);
const node=(tag,klass='',text)=>{const n=document.createElement(tag);if(klass)n.className=klass;if(text!==undefined)n.textContent=text;return n};
const textVal=el=>el&&el.tagName==='SELECT'?(el.value||''):el?.value||'';
function inputFor(f,inTable=false){
const x=document.createElement(f.type==='textarea'?'textarea':f.type==='select'?'select':'input');x.className='input';x.dataset.key=f.key;if(inTable)x.dataset.table=inTable;
if(x.tagName==='INPUT')x.type=f.type==='date'?'date':f.type==='datetime-local'?'datetime-local':f.type==='number'||f.type==='percent'?'number':'text';
if(f.type==='number'||f.type==='percent'){x.min='0';x.step=f.type==='percent'?'1':'any';if(f.type==='percent')x.max='100'}
if(f.type==='readonly')x.readOnly=true;
if(f.type==='textarea')x.rows=inTable?2:2;
if(f.type==='select'){const blank=node('option','', 'Seçiniz');blank.value='';x.append(blank);f.opts.split('|').forEach(value=>{const o=node('option','',value);o.value=value;x.append(o)})}
if(x.type==='text'&&!x.readOnly)x.placeholder='Bilgi giriniz';
return x;
}
function mirrorFor(ctrl){const mirror=node('div','print-value');mirror.setAttribute('aria-hidden','true');ctrl._mirror=mirror;syncMirror(ctrl);return mirror}
function syncMirror(ctrl){if(ctrl._mirror)ctrl._mirror.textContent=textVal(ctrl)}
function build(){
sections.forEach((section,i)=>{
const article=node('section','panel section');article.id='section_'+i;const h=node('h2','section-title',String(i+1)+'. '+section.title);article.append(h);const body=node('div','section-body');
if(section.fields){const grid=node('div','fields');section.fields.forEach(f=>{
const wrap=node('div','field'+(f.width===2?' w2':f.width===4?' w4':''));const label=node('label','',f.label);label.htmlFor='f_'+f.key;
const ctrl=inputFor(f);ctrl.id='f_'+f.key;wrap.append(label,ctrl,mirrorFor(ctrl));grid.append(wrap);
});body.append(grid)}
if(section.table){const tableName=section.table,def=tables[tableName],bar=node('div','table-head');bar.append(node('p','',def.title));const add=node('button','add','+ Satır Ekle');add.type='button';add.dataset.add=tableName;bar.append(add);body.append(bar);
const wrap=node('div','table-wrap'),table=node('table','table');table.dataset.table=tableName;const thead=node('thead'),tr=node('tr');const no=node('th','', 'No');no.style.width='38px';tr.append(no);def.columns.forEach(col=>tr.append(node('th','',col.label)));const act=node('th','remove','');act.style.width='43px';tr.append(act);thead.append(tr);table.append(thead,node('tbody'));wrap.append(table);body.append(wrap)}
article.append(body);$('formSections').append(article);if(section.table)addRow(section.table);
});
}
function addRow(name,values={}){
const tbody=document.querySelector('table[data-table="'+name+'"] tbody'),tr=node('tr');tr.append(node('td','count',String(tbody.rows.length+1)));
tables[name].columns.forEach(f=>{const cell=node('td'),ctrl=inputFor(f,name);ctrl.value=values[f.key]==null?'':String(values[f.key]);cell.append(ctrl,mirrorFor(ctrl));tr.append(cell)});
const c=node('td','remove'),del=node('button','delete','−');del.type='button';del.dataset.remove=name;del.title='Satırı kaldır';del.setAttribute('aria-label','Satırı kaldır');c.append(del);tr.append(c);tbody.append(tr);updateStats();
}
function readFields(){const data={};allFields.forEach(f=>data[f.key]=$('f_'+f.key).value);return data}
function readRows(name){return [...document.querySelectorAll('table[data-table="'+name+'"] tbody tr')].map(row=>Object.fromEntries([...row.querySelectorAll('[data-key]')].map(c=>[c.dataset.key,c.value]))).filter(row=>Object.values(row).some(x=>String(x).trim()!==''))}
function setField(key,value){const c=$('f_'+key);if(!c)return;c.value=value==null?'':String(value);if(c.tagName==='TEXTAREA')grow(c);syncMirror(c)}
function loadRows(name,rows){const tbody=document.querySelector('table[data-table="'+name+'"] tbody');tbody.replaceChildren();(rows?.length?rows:[{}]).forEach(r=>addRow(name,r));updateStats()}
function grow(t){if(t.tagName!=='TEXTAREA')return;t.style.height='auto';t.style.height=Math.max(64,t.scrollHeight)+'px'}
function between(a,b){if(!a||!b)return null;const start=new Date(a).getTime(),end=new Date(b).getTime();return Number.isFinite(start)&&Number.isFinite(end)&&end>=start?(end-start)/60000:null}
function duration(min){if(min==null||!Number.isFinite(min))return '';min=Math.floor(min);const days=Math.floor(min/1440),hours=Math.floor(min%1440/60),mins=min%60;return(days?days+' gün ':'')+(hours?hours+' sa ':'')+mins+' dk'}
function numeric(id){const raw=$('f_'+id)?.value;return raw==null||raw===''?null:Number(raw)}
function stat(id,value,state=''){const x=$(id);x.textContent=value;const card=x.closest('.metric');card.classList.remove('good','bad','warn');if(state)card.classList.add(state)}
function updateStats(){
const start=$('f_outageStart')?.value||'',resumed=$('f_serviceResumedAt')?.value||'',returned=$('f_actualReturn')?.value||'';
const startDate=start?new Date(start):null,now=new Date(),validStart=startDate&&Number.isFinite(startDate.getTime())&&startDate<=now;
const elapsed=validStart?between(start,returned||now):null;
setField('outageDuration',elapsed==null?'':duration(elapsed));stat('statDuration',elapsed==null?'—':duration(elapsed));
const rto=numeric('rtoMins'),rpo=numeric('rpoMins'),loss=numeric('actualLossMins');
// RTO, kritik hizmetin asgari düzeyde geri açıldığı an ile ölçülür.
const recovery=validStart?between(start,resumed||now):null;
if(rto===null||recovery===null)stat('statRto','—');
else if(recovery>rto)stat('statRto','Hedef aşıldı','bad');
else if(!resumed&&rto-recovery<=Math.max(10,rto*.2))stat('statRto','Sınıra yakın','warn');
else stat('statRto',resumed?'Hedef içinde':'Süre içinde','good');
if(rpo===null||loss===null)stat('statRpo','—');else stat('statRpo',loss<=rpo?'Hedef içinde':'Hedef aşıldı',loss<=rpo?'good':'bad');
const steps=readRows('steps');const done=steps.filter(x=>x.state==='Tamamlandı').length;
stat('statSteps',done+' / '+steps.length,steps.length&&done===steps.length?'good':'');
}
function notify(message){const t=$('toast');t.textContent=message;t.classList.add('show');clearTimeout(notify.timer);notify.timer=setTimeout(()=>t.classList.remove('show'),4000)}
/* Dikey Excel, çevrimdışı XLSX: Plan alanları dikey, tekrarlanan kayıtlar ayrı sayfalarda. */
function exportExcel(){try{
const data=[['Bölüm','Alan','Değer','Alan Kodu']];
sections.forEach(s=>(s.fields||[]).forEach(f=>data.push([s.title,f.label,$('f_'+f.key).value,f.key])));
const sheets={'Plan':data};
for(const [name,def] of Object.entries(tables)){const rows=readRows(name);sheets[def.sheet]=[['Sıra',...def.columns.map(f=>f.label)],...rows.map((r,i)=>[i+1,...def.columns.map(f=>r[f.key]||'')])]}
sheets.Bilgi=[['Anahtar','Değer'],['format','Olgun UI2 Kısa Plan v2'],['kurum',$('org').textContent.trim()]];
const id=($('f_planNo').value||'Kritik_Hizmet_Alternatif_Isleyis').replace(/[^a-zA-Z0-9_-]/g,'_').slice(0,70);
XlsxLocal.download(XlsxLocal.write(sheets),id+'.xlsx');notify('Dikey Excel dosyası oluşturuldu.');
}catch(e){console.error(e);notify('Excel dışarı aktarılamadı: '+e.message)}
}
async function importExcel(file){if(!file)return;try{
const wb=await XlsxLocal.read(file),rows=wb.Plan;if(!rows)throw new Error('Plan sayfası bulunamadı.');
const data={};rows.slice(1).forEach(row=>{if(row[3])data[String(row[3])]=row[2]==null?'':String(row[2])});
if(!allFields.some(f=>Object.prototype.hasOwnProperty.call(data,f.key)))throw new Error('Formla eşleşen alan bulunamadı.');
const imported={};for(const [name,def] of Object.entries(tables)){
const values=wb[def.sheet];if(!values){imported[name]=[];continue}
const headers=(values[0]||[]).map(x=>String(x||'').trim());
imported[name]=values.slice(1).map(row=>Object.fromEntries(def.columns.map((col,i)=>{const idx=headers.indexOf(col.label);return[col.key,String((idx>=0?row[idx]:row[i+1])??'')]}))).filter(r=>Object.values(r).some(v=>v.trim()));
}
allFields.forEach(f=>setField(f.key,data[f.key]??''));Object.entries(imported).forEach(([name,rows])=>loadRows(name,rows));
if(wb.Bilgi){const org=wb.Bilgi.find(r=>r[0]==='kurum');if(org&&org[1])setOrg(String(org[1]))}
updateStats();notify('Excel kayıtları forma aktarıldı.');
}catch(e){console.error(e);notify('Excel içeri aktarılamadı: '+e.message)}finally{$('fileInput').value=''}
}
/* Olgun UI2 kurumsal başlık: 5 normal tıklama (çift tıklama şart değil).
Logo aynı klasördeki logo.jpg'den yüklenir; geçerli görsel yoksa alan KAYBOLMAZ.
Seçilen logo ve düzenlenen başlık yerel saklamada korunur. */
const ORG_KEY='olgun_ui2_kurum_adi', LEGACY_ORG_KEY='olgun_ui_kurum_basligi', LOGO_KEY='olgun_ui2_logo_data';
function safeGet(key){try{return localStorage.getItem(key)}catch(_){return null}}
function safeSet(key,value){try{localStorage.setItem(key,value);return true}catch(_){return false}}
function cleanOrg(str){return String(str||'').replace(/\s+/g,' ').trim().slice(0,160)||'OLGUN BAŞKANLIĞI'}
function setOrg(value){const v=cleanOrg(value);$('org').textContent=v;safeSet(ORG_KEY,v)}
function makeFiveClick(target,perform,label,{oneClickIf=()=>false}={}){
let taps=0,previous=0;
function click(){if(oneClickIf()){taps=0;perform();return}
const now=Date.now();taps=(now-previous>10000)?1:taps+1;previous=now;
if(taps>=5){taps=0;perform();return}
if(taps>=2)notify(label+' için '+(5-taps)+' tıklama daha.');
}
target.addEventListener('click',()=>{if(target.isContentEditable)return;click()});
return click;
}
function initOrg(){const n=$('org');n.textContent=cleanOrg(safeGet(ORG_KEY)||safeGet(LEGACY_ORG_KEY)||n.textContent);
let original='',cancel=false;
function edit(){if(n.isContentEditable)return;original=n.textContent;cancel=false;
n.contentEditable='true';n.setAttribute('aria-readonly','false');n.focus();
const range=document.createRange();range.selectNodeContents(n);const selection=window.getSelection();selection?.removeAllRanges();selection?.addRange(range);
notify('Kurum adı düzenlenebilir. Kaydetmek için Enter veya alan dışına tıklayın.');
}
const increment=makeFiveClick(n,edit,'Kurum adını düzenlemek');
n.addEventListener('keydown',e=>{
if(!n.isContentEditable){if(e.key==='Enter'||e.key===' '){e.preventDefault();increment()}return}
if(e.key==='Enter'){e.preventDefault();n.blur()}
if(e.key==='Escape'){e.preventDefault();cancel=true;n.textContent=original;n.blur()}
});
n.addEventListener('paste',e=>{if(!n.isContentEditable)return;e.preventDefault();
const value=(e.clipboardData?.getData('text/plain')||'').replace(/\s+/g,' ').slice(0,160);
const sel=window.getSelection();if(!sel?.rangeCount)return;const r=sel.getRangeAt(0);r.deleteContents();const textNode=document.createTextNode(value);r.insertNode(textNode);r.setStartAfter(textNode);r.collapse(true);sel.removeAllRanges();sel.addRange(r)
});
n.addEventListener('blur',()=>{if(!n.isContentEditable)return;n.contentEditable='false';n.setAttribute('aria-readonly','true');setOrg(cancel?original:n.textContent)});
}
function initLogo(){const box=$('logoBox'),img=$('logo'),chooser=$('logoFileInput');let shown=false;
function verify(){const ok=img.complete&&img.naturalWidth>0;box.classList.toggle('has-logo',ok);shown=ok;return ok}
img.addEventListener('load',verify);img.addEventListener('error',()=>{box.classList.remove('has-logo');shown=false});
const cached=safeGet(LOGO_KEY);
if(cached&&/^data:image\/(png|jpeg|webp|gif);base64,/i.test(cached))img.src=cached;
// Complete may already be true (e.g. logo.jpg from the browser cache).
if(img.complete)verify();
const openChooser=()=>{chooser.value='';chooser.click()};
makeFiveClick(box,openChooser,'Logoyu değiştirmek',{oneClickIf:()=>!shown});
chooser.addEventListener('change',async()=>{const file=chooser.files?.[0];if(!file)return;
if(!/^image\/(png|jpeg|webp|gif)$/.test(file.type)){notify('PNG, JPG, WebP veya GIF dosyası seçin.');return}
const objectUrl=URL.createObjectURL(file);
try{
const photo=new Image();await new Promise((resolve,reject)=>{photo.onload=resolve;photo.onerror=()=>reject(new Error('Logo görseli açılamadı.'));photo.src=objectUrl});
const maxSide=480,ratio=Math.min(1,maxSide/Math.max(photo.naturalWidth,photo.naturalHeight));
const canvas=document.createElement('canvas');canvas.width=Math.max(1,Math.round(photo.naturalWidth*ratio));canvas.height=Math.max(1,Math.round(photo.naturalHeight*ratio));
const context=canvas.getContext('2d');context.drawImage(photo,0,0,canvas.width,canvas.height);
const data=canvas.toDataURL('image/webp',.9);
// Set image only after decode so that the logo area never vanishes on an invalid file.
img.src=data;try{await img.decode()}catch(_){}
verify();const persisted=safeSet(LOGO_KEY,data);
notify(persisted?'Logo değiştirildi ve bu tarayıcıya kaydedildi.':'Logo değiştirildi. Tarayıcı yerel kayda izin vermediği için kalıcı saklanamadı.');
}catch(err){notify('Logo açılamadı: '+err.message)}finally{URL.revokeObjectURL(objectUrl)}
});
}
function init(){build();initOrg();initLogo();
$('formSections').addEventListener('click',e=>{const add=e.target.closest('[data-add]');if(add){addRow(add.dataset.add);return}const del=e.target.closest('[data-remove]');if(del){const name=del.dataset.remove,tb=del.closest('tbody');del.closest('tr').remove();if(!tb.rows.length)addRow(name);[...tb.rows].forEach((tr,i)=>tr.cells[0].textContent=i+1);updateStats()}});
$('formSections').addEventListener('input',e=>{if(e.target.tagName==='TEXTAREA')grow(e.target);syncMirror(e.target);updateStats()});$('formSections').addEventListener('change',e=>{syncMirror(e.target);updateStats()});
$('importBtn').addEventListener('click',()=>$('fileInput').click());$('fileInput').addEventListener('change',e=>importExcel(e.target.files[0]));$('exportBtn').addEventListener('click',exportExcel);$('printBtn').addEventListener('click',()=>{document.querySelectorAll('textarea').forEach(grow);updateStats();window.print()});
updateStats();setInterval(updateStats,60000);
}
document.addEventListener('DOMContentLoaded',init);
</script>
</body>
</html>













