BT Helpdesk'in Görünmeyen Dünyası: Tüm Hizmetleri Tek Dashboard'da Topladım

Bir kurumda BT Helpdesk denildiğinde çoğu kişinin aklına yalnızca açılan destek kayıtları gelir. Oysa Helpdesk ekipleri, günlük operasyonların kesintisiz devam etmesi için onlarca farklı hizmeti aynı anda yönetir.

Yeni bir bilgisayarın kurulması, yazıcı desteği, telefon işlemleri, kullanıcı hesapları, toplantı odaları, kamera sistemleri, ağ bağlantıları, işe giriş ve işten ayrılış süreçleri... Bunların tamamı Helpdesk ekibinin sorumluluk alanına girer. Ancak bu hizmetlerin tamamını tek bir yerde görmek çoğu zaman mümkün değildir.

Bu ihtiyacı karşılamak amacıyla, ekibimizin sunduğu hizmetleri tek bir ekranda toplayan, BT Hizmet Kataloğu Dashboard hazırladım.


Tek Bakışta Tüm Hizmetler:
Dashboard'ın temel amacı, Helpdesk ekibinin hangi alanlarda hizmet verdiğini açık ve anlaşılır şekilde göstermektir. Uygulama iki parçadan oluşmaktadır. Birinci kısım, aşağıda göründüğü gibi dashboard panelidir.

İkinci kısım verilerin bulunduğu data kısmıdır. Excel içinde oluşturulan veri aşağıda görüldüğü gibidir. Yaptığınız sorumluluk alanı ile ilgili herhangi bir birim içinde uygulanabilir bir yapıdadır. 

Tablo hakkında bilgi: ("A" sütunu Ana hizmet, "B" sütunu Alt hizmet alanının içermelidir. "A" ve "B" verisi 3. satır itibariyle başlamalıdır. Sorumluluk alanları ile ilgili veri "C1" sütunu başlık, "C2" adet içermelidir. Sorumluluk alanlarını arttırabilir, eksiltebilirsiniz.) Ör: 3 Adet Sorumluluk alanı yansıtmak isterseniz (C1, D1, E1) başlık, (C2, D2, E2) adet içermelidir.

Dashboard da veriyi "Excel ile İçe Aktar" butonuna tıkladığınızda, datanın bulunduğu Excel dosyasını içeri aktarmış olacağız. Girmiş olduğunuz tüm hizmetleri bir arada görebilirsiniz. Veriyi dışa aktarabilir, PDF olarak kaydedip paylaşabilirsiniz. 

Dashboard:

Üst bölümde;
  • Destek verilen personel sayısı,
  • Yönetilen bilgisayar sayısı,
  • Ortak yazıcılar,
  • Tarayıcılar,
  • Projeksiyon cihazları,
  • Telefonlar,
  • Baz istasyonları, gibi sorumluluğumuzdaki temel varlıklar yer almaktadır.

Alt bölümde ise hizmetler; 
  • Yazılım İşlemleri, 
  • Yazıcı Hizmetleri, 
  • Personel İşlemleri, 
  • Telefon Hizmetleri, 
  • Toplantı Hizmetleri, 
  • KGS, 
  • İşe Başlama, 
  • İşten Ayrılma, 
  • Kamera, 
  • Ağ Hizmetleri, 
  • Donanım İşlemleri, 
  • Mobil Destek, 
  • E-İmza, 
  • EBYS ve diğer başlıklar altında kategorize edilmiştir. Her ana hizmet altında gerçekleştirilen alt hizmetler de detaylı olarak listelenmektedir.

PDF Kaydet / Yazdır:
Şablonda içindeki butona basarak kaydedebilir, Önlü arkalı yazdırabilir, paylaşabilirsiniz.



Bu Dashboard Neden Hazırlandı?
Kurumlarda BT ekiplerinin yaptığı işler çoğu zaman yalnızca destek kayıtları üzerinden değerlendirilir. Ancak günlük operasyonların önemli bir bölümü standart hizmetler olarak yürütülür ve bu çalışmaların kapsamı her zaman görünür olmaz.

Bu dashboard ile amaçlanan;
  • Helpdesk ekibinin hizmet kapsamını açık şekilde göstermek,
  • Sorumluluk alanlarını tek ekranda toplamak,
  • Yöneticilere hızlı ve anlaşılır bir genel görünüm sunmak,
  • Yeni başlayan ekip üyeleri için hizmet kataloğu oluşturmak,
  • Kurum içerisinde verilen BT hizmetlerini standart hale getirmektir.

Yönetim İçin Sağladığı Değer:
Yöneticiler için en önemli sorulardan biri şudur: 
"BT Helpdesk ekibi tam olarak hangi hizmetleri sunuyor?
Bu dashboard sayesinde bu sorunun cevabı tek bir ekranda görülebilmektedir.

Her hizmet başlığı altında yapılan işlemler açıkça listelendiği için ekiplerin görev alanları daha anlaşılır hale gelmektedir. Aynı zamanda destek verilen kullanıcı ve yönetilen cihaz sayıları da hizmet kapsamını daha net ortaya koymaktadır.


Kurumsal Hafıza Oluşturuyor:
BT ekiplerinde bilgi yalnızca kişilerde kaldığında, görev değişiklikleri veya personel ayrılıkları süreçleri zorlaştırabilir.

Hazırlanan bu dashboard, kurumun sunduğu BT hizmetlerini dokümante ederek ortak bir hizmet kataloğu oluşturur. Böylece ekip üyeleri aynı standart üzerinden çalışabilir ve hizmet kapsamı herkes tarafından kolayca anlaşılabilir.


Sonuç:
BT Helpdesk yalnızca arızalara müdahale eden bir ekip değildir. Kullanıcılardan cihazlara, yazılımlardan ağ altyapısına kadar kurumun günlük operasyonlarını destekleyen geniş bir hizmet ağı sunar.

Hazırladığım BT Hizmet Kataloğu Dashboard, bu hizmetleri tek bir ekranda görünür hale getirerek hem yöneticilere hem de ekibe ortak bir bakış açısı kazandırmaktadır.

Bir hizmetin değeri, yalnızca yapıldığında değil, görünür hale getirildiğinde de anlaşılır. Bu dashboard'ın amacı tam olarak budur: Helpdesk ekibinin sunduğu hizmetleri sade, düzenli ve herkes tarafından anlaşılabilir bir şekilde ortaya koymaktır.


Dashboard Oluşturmak için:
Aşağıdaki kodu kopyalayıp, not defterine yapıştırınız. Uzantıyı ".html" yapmanız sonrası, Şablonu kullanabilirsiniz. Not defteri ile düzenleme ile açtığınızda "Ctrl+F" ile başlıkları bulup değiştirme ve ardından kaydetme sonrasında, Başlık, Birim ve Müdürlük ifadeleri yansıyacak şekilde kullanabilirsiniz.


HTML Kod:
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>BT Hizmet Kataloğu</title>
    <script src="https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js"></script>
    <style>
        :root {
            --m: #0b4f8a;
            --bg: #eef4f8;
            --shadow: 0 3px 10px rgba(0,0,0,.12);
            --radius: 8px;
        }
        * { margin: 0; padding: 0; box-sizing: border-box; font-family: "Segoe UI", Arial, sans-serif; }
        body { background: var(--bg); padding: 8px; }
        
        .header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #fff; border: 2px solid #000; border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: 8px; gap: 15px; }
        .left { display: flex; align-items: center; gap: 12px; }
        .logo img { height: 46px; }
        .title1 { font-size: 22px; font-weight: 700; }
        .title2 { font-size: 11px; }
        
        /* Başlıktaki alan kalın siyah çerçeve ve kalın yazılar */
        .center-kpis { display: flex; gap: 10px; justify-content: center; align-items: center; flex-grow: 1; }
        .header-kpi { background: #fff; border: 2px solid #000; border-radius: var(--radius); padding: 2px 12px; text-align: center; min-width: 110px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
        .header-kpi h2 { font-size: 16px; color: #000; font-weight: 700; margin-bottom: 0px; line-height: 1.2; }
        .header-kpi div { font-size: 11px; font-weight: 700; color: #000; }
        
        .right { font-size: 14px; font-weight: 700; white-space: nowrap; text-align: right; }
        
        /* Alttaki özet alanındaki kutucuklar: Mavi ince çizgili, kalın olmayan (normal) içerik */
        .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 20px; }
        .kpi { background: #fff; border: 1px solid var(--m); border-radius: var(--radius); box-shadow: var(--shadow); padding: 4px 6px; text-align: center; }
        .kpi h2 { font-size: 20px; margin-bottom: 1px; color: var(--m); font-weight: 400; line-height: 1.1; }
        .kpi div { font-size: 11px; font-weight: 400; color: #333; }
        
        .panel { background: #fff; border: 1px solid var(--m); border-radius: 10px; padding: 10px; box-shadow: var(--shadow); }
        .msg { font-size: 13px; font-weight: 600; color: var(--m); margin-bottom: 8px; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 8px; }
        
        /* Alt hizmet kartları ve listesi - Çerçevenin metnin bittiği yerde kapanması için dikey paddingler minimize edildi */
        .card { background: #fff; border: 1px solid var(--m); border-radius: 8px; overflow: hidden; box-shadow: 0 2px 6px rgba(0,0,0,.08); transition: .2s; height: max-content; }
        .card:hover { transform: translateY(-2px); }
        .cardhead { background: var(--m); color: #fff; padding: 5px 10px; font-size: 13px; font-weight: 700; line-height: 1.2; }
        .card ul { list-style: none; padding: 0; margin: 0; }
        .card li { padding: 3px 10px; font-size: 12px; border-top: 1px solid #edf2f7; line-height: 1.2; }
        
        .empty { padding: 35px; text-align: center; color: #777; }
        .toolbar { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
        button { background: var(--m); color: #fff; border: none; padding: 8px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; transition: .2s; }
        button:hover { opacity: .92; }
        input[type=file] { display: none; }
        
        @page { size: A4 landscape; margin: 4mm; }
        @media print {
            body { padding: 0; background: #fff; }
            .toolbar, .msg { display: none!important; }
            .kpis { margin-bottom: 20px !important; }
            .panel { border: none !important; padding: 0 !important; box-shadow: none !important; }
            .card { break-inside: avoid; page-break-inside: avoid; height: max-content; }
            .cardhead { page-break-after: avoid; }
            * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
        }
    </style>
</head>
<body>

<div class="header">
    <div class="left">
        <div class="logo"><img src="logo.jpg" onerror="this.style.display='none'"></div>
        <div>
            <div class="title1">BT Hizmet Kataloğu</div>
            <div class="title2">Helpdesk</div>
        </div>
    </div>
    
    <div class="center-kpis">
        <div class="header-kpi"><h2 id="ana">0</h2><div>Ana Hizmet</div></div>
        <div class="header-kpi"><h2 id="alt">0</h2><div>Alt Hizmet</div></div>
    </div>
    
    <div class="right">MYO</div>
</div>

<div class="kpis" id="kpiContainer"></div>

<div class="panel">
    <div id="msg" class="msg">Excel dosyası yükleyin.</div>
    <div id="grid" class="grid">
        <div class="empty">BT Hizmet Kataloğunu görüntülemek için Excel dosyası seçiniz.</div>
    </div>
    <div class="toolbar">
        <button id="btnImport">📥 Excel İçe Aktar</button>
        <button id="btnExport">📤 Excel Dışa Aktar</button>
        <button onclick="window.print()">🖨 Yazdır / PDF</button>
        <input id="excelFile" type="file" accept=".xlsx,.xls">
    </div>
</div>

<script>
const App = {
    rows: [],
    serviceMap: new Map(),
    dynamicKpis: [],

    init() {
        this.ui = {
            grid: document.getElementById("grid"),
            msg: document.getElementById("msg"),
            ana: document.getElementById("ana"),
            alt: document.getElementById("alt"),
            kpiContainer: document.getElementById("kpiContainer"),
            file: document.getElementById("excelFile"),
            importBtn: document.getElementById("btnImport"),
            exportBtn: document.getElementById("btnExport")
        };

        if (this.ui.importBtn && this.ui.file) {
            this.ui.importBtn.addEventListener("click", () => {
                this.ui.file.click();
            });
        }
        
        if (this.ui.file) {
            this.ui.file.addEventListener("change", (e) => {
                this.readExcel(e);
            });
        }
        
        if (this.ui.exportBtn) {
            this.ui.exportBtn.addEventListener("click", () => {
                this.exportExcel();
            });
        }
    },

    reset() {
        this.rows = [];
        this.serviceMap.clear();
        this.dynamicKpis = [];
    },

    formatNumber(val) {
        if (val === undefined || val === null || val === "") return "0";
        const str = val.toString().trim();
        const clean = str.replace(/\./g, '').replace(/,/g, '');
        const num = Number(clean);
        if (!isNaN(num) && clean !== "") {
            return num.toLocaleString("tr-TR");
        }
        return str;
    },

    readExcel(e) {
        const file = e.target.files[0];
        if (!file) return;
        
        const reader = new FileReader();
        reader.onload = (x) => {
            try {
                const dataData = new Uint8Array(x.target.result);
                const wb = XLSX.read(dataData, { type: "array" });
                const sheetName = wb.SheetNames[0];
                const sheet = wb.Sheets[sheetName];
                const data = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: "" });
                
                this.parseExcel(data);
            } catch (err) {
                alert("Excel dosyası çözümlenirken hata oluştu: " + err.message);
                console.error(err);
            }
        };
        reader.readAsArrayBuffer(file);
    },

    parseExcel(data) {
        try {
            this.reset();
            if (!data || data.length === 0) {
                alert("Dosya boş veya geçersiz.");
                return;
            }

            const headers = data[0] || [];
            const values = data[1] || [];
            
            for (let col = 2; col <= 8; col++) {
                if (headers[col] !== undefined && headers[col] !== null && headers[col].toString().trim() !== "") {
                    const titleStr = headers[col].toString().trim();
                    const valueStr = (values[col] !== undefined && values[col] !== null) ? values[col].toString().trim() : "0";
                    this.dynamicKpis.push({
                        title: titleStr,
                        value: valueStr
                    });
                }
            }

            for (let i = 2; i < data.length; i++) {
                const row = data[i];
                if (!row) continue;
                
                const ana = (row[0] !== undefined && row[0] !== null) ? row[0].toString().trim() : "";
                const alt = (row[1] !== undefined && row[1] !== null) ? row[1].toString().trim() : "";
                
                if (ana !== "" && alt !== "") {
                    this.rows.push({ ana, alt });
                    if (!this.serviceMap.has(ana)) {
                        this.serviceMap.set(ana, []);
                    }
                    this.serviceMap.get(ana).push(alt);
                }
            }

            this.render();
        } catch (err) {
            alert("Veriler işlenirken hata oluştu: " + err.message);
            console.error(err);
        } finally {
            if (this.ui.file) this.ui.file.value = "";
        }
    },

    render() {
        try {
            if (this.serviceMap.size === 0) {
                this.ui.grid.innerHTML = '<div class="empty">Gösterilecek veri bulunamadı veya geçersiz format.</div>';
                this.ui.msg.textContent = "Veri bulunamadı.";
            } else {
                let cardsHtml = "";
                for (const [service, list] of this.serviceMap) {
                    let itemsHtml = "";
                    for (const item of list) {
                        itemsHtml += `<li>${item}</li>`;
                    }
                    cardsHtml += `
                        <div class="card">
                            <div class="cardhead">${service} (${this.formatNumber(list.length)})</div>
                            <ul>${itemsHtml}</ul>
                        </div>
                    `;
                }
                this.ui.grid.innerHTML = cardsHtml;
                this.ui.msg.textContent = `Excel başarıyla yüklendi. Toplam ${this.formatNumber(this.rows.length)} alt hizmet listelendi.`;
            }

            this.ui.ana.textContent = this.formatNumber(this.serviceMap.size);
            this.ui.alt.textContent = this.formatNumber(this.rows.length);

            let kpisHtml = "";
            this.dynamicKpis.forEach(kpi => {
                kpisHtml += `
                    <div class="kpi">
                        <h2>${this.formatNumber(kpi.value)}</h2>
                        <div>${kpi.title}</div>
                    </div>
                `;
            });
            this.ui.kpiContainer.innerHTML = kpisHtml;
        } catch (err) {
            console.error("Arayüz güncellenirken hata:", err);
        }
    },

    exportExcel() {
        try {
            if (!this.rows.length) {
                alert("Aktarılacak veya dışa aktarılacak yüklü bir veri bulunamadı. Önce Excel yükleyin.");
                return;
            }

            const firstRow = ["Ana Hizmet", "Alt Hizmet"];
            this.dynamicKpis.forEach(k => firstRow.push(k.title));
            
            const secondRow = ["", ""];
            this.dynamicKpis.forEach(k => secondRow.push(k.value));
            
            const aoa = [firstRow, secondRow];

            this.rows.forEach((r) => {
                const rowData = [r.ana, r.alt];
                this.dynamicKpis.forEach(() => rowData.push(""));
                aoa.push(rowData);
            });

            const ws = XLSX.utils.aoa_to_sheet(aoa);
            const wb = XLSX.utils.book_new();
            XLSX.utils.book_append_sheet(wb, ws, "BT Hizmet Kataloğu");
            XLSX.writeFile(wb, "BT_Hizmet_Dashboard_v49.xlsx");
        } catch (err) {
            alert("Excel dışa aktarılırken hata oluştu: " + err.message);
            console.error(err);
        }
    }
};

document.addEventListener("DOMContentLoaded", () => App.init());
</script>
</body>
</html>


Yazdırma/PDF Tek sayfaya Sığsın:
Ön İzleme:

Yazdırma:


HTML Kod: (Yazdırma/PDF Tek sayfaya Sığsın)
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>BT Hizmet Kataloğu - Tek Sayfa (Tek Sayfa)</title>
    <script src="https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js"></script>
    <style>
        :root {
            --m: #0b4f8a;
            --bg: #eef4f8;
            --shadow: 0 3px 10px rgba(0,0,0,.12);
            --radius: 8px;
        }
        * { margin: 0; padding: 0; box-sizing: border-box; font-family: "Segoe UI", Arial, sans-serif; }
        body { background: var(--bg); padding: 8px; }
        
        .header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #fff; border: 2px solid #000; border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: 8px; gap: 15px; }
        .left { display: flex; align-items: center; gap: 12px; }
        .logo img { height: 46px; }
        .title1 { font-size: 22px; font-weight: 700; }
        .title2 { font-size: 11px; }
        
        /* Başlıktaki alan kalın siyah çerçeve ve kalın yazılar */
        .center-kpis { display: flex; gap: 10px; justify-content: center; align-items: center; flex-grow: 1; }
        .header-kpi { background: #fff; border: 2px solid #000; border-radius: var(--radius); padding: 2px 12px; text-align: center; min-width: 110px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
        .header-kpi h2 { font-size: 16px; color: #000; font-weight: 700; margin-bottom: 0px; line-height: 1.2; }
        .header-kpi div { font-size: 11px; font-weight: 700; color: #000; }
        
        .right { font-size: 14px; font-weight: 700; white-space: nowrap; text-align: right; }
        
        /* Alttaki özet alanındaki kutucuklar: Mavi ince çizgili, kalın olmayan (normal) içerik */
        .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 20px; }
        .kpi { background: #fff; border: 1px solid var(--m); border-radius: var(--radius); box-shadow: var(--shadow); padding: 4px 6px; text-align: center; }
        .kpi h2 { font-size: 20px; margin-bottom: 1px; color: var(--m); font-weight: 400; line-height: 1.1; }
        .kpi div { font-size: 11px; font-weight: 400; color: #333; }
        
        .panel { background: #fff; border: 1px solid var(--m); border-radius: 10px; padding: 10px; box-shadow: var(--shadow); }
        .msg { font-size: 13px; font-weight: 600; color: var(--m); margin-bottom: 8px; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 8px; }
        
        /* Alt hizmet kartları ve listesi */
        .card { background: #fff; border: 1px solid var(--m); border-radius: 8px; overflow: hidden; box-shadow: 0 2px 6px rgba(0,0,0,.08); transition: .2s; height: max-content; }
        .card:hover { transform: translateY(-2px); }
        .cardhead { background: var(--m); color: #fff; padding: 5px 10px; font-size: 13px; font-weight: 700; line-height: 1.2; }
        .card ul { list-style: none; padding: 0; margin: 0; }
        .card li { padding: 3px 10px; font-size: 12px; border-top: 1px solid #edf2f7; line-height: 1.2; }
        
        .empty { padding: 35px; text-align: center; color: #777; }
        .toolbar { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
        button { background: var(--m); color: #fff; border: none; padding: 8px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; transition: .2s; }
        button:hover { opacity: .92; }
        input[type=file] { display: none; }
        
        /* Yazdırma Kuralları (Tek Sayfa ve Boşluksuz Tasarım) */
        @page { size: A4 landscape; margin: 4mm; }
        @media print {
            body { padding: 0; margin: 0; background: #fff; }
            .toolbar, .msg { display: none!important; }
            .kpis { margin-bottom: 12px !important; gap: 4px !important; }
            .panel { border: none !important; padding: 0 !important; box-shadow: none !important; }
            
            /* Sayfada boş yer kalmaması ve kartların satırı tam kaplaması için auto-fit yapısı */
            .grid { 
                display: grid !important; 
                grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important; 
                gap: 5px !important; 
            }
            
            /* Alt hizmet kutucuklarının içi tamamen sıkıştırıldı (boşluklar kaldırıldı) */
            .card { 
                break-inside: avoid; 
                page-break-inside: avoid; 
                height: max-content; 
                border: 1px solid var(--m) !important;
                box-shadow: none !important;
                margin: 0 !important;
                padding: 0 !important;
            }
            .cardhead { 
                page-break-after: avoid; 
                padding: 2px 5px !important; /* Başlık iç boşluğu minimize edildi */
                font-size: 11px !important;
                line-height: 1.1 !important;
            }
            .card ul { 
                padding: 0 !important; 
                margin: 0 !important; 
            }
            .card li { 
                padding: 1px 5px !important; /* Alt hizmet satır içi boşlukları sıfıra yakın daraltıldı */
                font-size: 10px !important;
                line-height: 1.1 !important;
                margin: 0 !important;
                border-top: 1px solid #ddd !important;
            }
            
            /* Genel ölçeklendirmeyi tek sayfaya zorlamak için hafifçe optimize et */
            * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
        }
    </style>
</head>
<body>

<div class="header">
    <div class="left">
        <div class="logo"><img src="logo.jpg" onerror="this.style.display='none'"></div>
        <div>
            <div class="title1">BT Hizmet Kataloğu</div>
            <div class="title2">Helpdesk</div>
        </div>
    </div>
    
    <div class="center-kpis">
        <div class="header-kpi"><h2 id="ana">0</h2><div>Ana Hizmet</div></div>
        <div class="header-kpi"><h2 id="alt">0</h2><div>Alt Hizmet</div></div>
    </div>
    
    <div class="right">MYO</div>
</div>

<div class="kpis" id="kpiContainer"></div>

<div class="panel">
    <div id="msg" class="msg">Excel dosyası yükleyin.</div>
    <div id="grid" class="grid">
        <div class="empty">BT Hizmet Kataloğunu görüntülemek için Excel dosyası seçiniz.</div>
    </div>
    <div class="toolbar">
        <button id="btnImport">📥 Excel İçe Aktar</button>
        <button id="btnExport">📤 Excel Dışa Aktar</button>
        <button onclick="window.print()">🖨 Yazdır / PDF</button>
        <input id="excelFile" type="file" accept=".xlsx,.xls">
    </div>
</div>

<script>
const App = {
    rows: [],
    serviceMap: new Map(),
    dynamicKpis: [],

    init() {
        this.ui = {
            grid: document.getElementById("grid"),
            msg: document.getElementById("msg"),
            ana: document.getElementById("ana"),
            alt: document.getElementById("alt"),
            kpiContainer: document.getElementById("kpiContainer"),
            file: document.getElementById("excelFile"),
            importBtn: document.getElementById("btnImport"),
            exportBtn: document.getElementById("btnExport")
        };

        if (this.ui.importBtn && this.ui.file) {
            this.ui.importBtn.addEventListener("click", () => {
                this.ui.file.click();
            });
        }
        
        if (this.ui.file) {
            this.ui.file.addEventListener("change", (e) => {
                this.readExcel(e);
            });
        }
        
        if (this.ui.exportBtn) {
            this.ui.exportBtn.addEventListener("click", () => {
                this.exportExcel();
            });
        }
    },

    reset() {
        this.rows = [];
        this.serviceMap.clear();
        this.dynamicKpis = [];
    },

    formatNumber(val) {
        if (val === undefined || val === null || val === "") return "0";
        const str = val.toString().trim();
        const clean = str.replace(/\./g, '').replace(/,/g, '');
        const num = Number(clean);
        if (!isNaN(num) && clean !== "") {
            return num.toLocaleString("tr-TR");
        }
        return str;
    },

    readExcel(e) {
        const file = e.target.files[0];
        if (!file) return;
        
        const reader = new FileReader();
        reader.onload = (x) => {
            try {
                const dataData = new Uint8Array(x.target.result);
                const wb = XLSX.read(dataData, { type: "array" });
                const sheetName = wb.SheetNames[0];
                const sheet = wb.Sheets[sheetName];
                const data = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: "" });
                
                this.parseExcel(data);
            } catch (err) {
                alert("Excel dosyası çözümlenirken hata oluştu: " + err.message);
                console.error(err);
            }
        };
        reader.readAsArrayBuffer(file);
    },

    parseExcel(data) {
        try {
            this.reset();
            if (!data || data.length === 0) {
                alert("Dosya boş veya geçersiz.");
                return;
            }

            const headers = data[0] || [];
            const values = data[1] || [];
            
            for (let col = 2; col <= 8; col++) {
                if (headers[col] !== undefined && headers[col] !== null && headers[col].toString().trim() !== "") {
                    const titleStr = headers[col].toString().trim();
                    const valueStr = (values[col] !== undefined && values[col] !== null) ? values[col].toString().trim() : "0";
                    this.dynamicKpis.push({
                        title: titleStr,
                        value: valueStr
                    });
                }
            }

            for (let i = 2; i < data.length; i++) {
                const row = data[i];
                if (!row) continue;
                
                const ana = (row[0] !== undefined && row[0] !== null) ? row[0].toString().trim() : "";
                const alt = (row[1] !== undefined && row[1] !== null) ? row[1].toString().trim() : "";
                
                if (ana !== "" && alt !== "") {
                    this.rows.push({ ana, alt });
                    if (!this.serviceMap.has(ana)) {
                        this.serviceMap.set(ana, []);
                    }
                    this.serviceMap.get(ana).push(alt);
                }
            }

            this.render();
        } catch (err) {
            alert("Veriler işlenirken hata oluştu: " + err.message);
            console.error(err);
        } finally {
            if (this.ui.file) this.ui.file.value = "";
        }
    },

    render() {
        try {
            if (this.serviceMap.size === 0) {
                this.ui.grid.innerHTML = '<div class="empty">Gösterilecek veri bulunamadı veya geçersiz format.</div>';
                this.ui.msg.textContent = "Veri bulunamadı.";
            } else {
                let cardsHtml = "";
                for (const [service, list] of this.serviceMap) {
                    let itemsHtml = "";
                    for (const item of list) {
                        itemsHtml += `<li>${item}</li>`;
                    }
                    cardsHtml += `
                        <div class="card">
                            <div class="cardhead">${service} (${this.formatNumber(list.length)})</div>
                            <ul>${itemsHtml}</ul>
                        </div>
                    `;
                }
                this.ui.grid.innerHTML = cardsHtml;
                this.ui.msg.textContent = `Excel başarıyla yüklendi. Toplam ${this.formatNumber(this.rows.length)} alt hizmet listelendi.`;
            }

            this.ui.ana.textContent = this.formatNumber(this.serviceMap.size);
            this.ui.alt.textContent = this.formatNumber(this.rows.length);

            let kpisHtml = "";
            this.dynamicKpis.forEach(kpi => {
                kpisHtml += `
                    <div class="kpi">
                        <h2>${this.formatNumber(kpi.value)}</h2>
                        <div>${kpi.title}</div>
                    </div>
                `;
            });
            this.ui.kpiContainer.innerHTML = kpisHtml;
        } catch (err) {
            console.error("Arayüz güncellenirken hata:", err);
        }
    },

    exportExcel() {
        try {
            if (!this.rows.length) {
                alert("Aktarılacak veya dışa aktarılacak yüklü bir veri bulunamadı. Önce Excel yükleyin.");
                return;
            }

            const firstRow = ["Ana Hizmet", "Alt Hizmet"];
            this.dynamicKpis.forEach(k => firstRow.push(k.title));
            
            const secondRow = ["", ""];
            this.dynamicKpis.forEach(k => secondRow.push(k.value));
            
            const aoa = [firstRow, secondRow];

            this.rows.forEach((r) => {
                const rowData = [r.ana, r.alt];
                this.dynamicKpis.forEach(() => rowData.push(""));
                aoa.push(rowData);
            });

            const ws = XLSX.utils.aoa_to_sheet(aoa);
            const wb = XLSX.utils.book_new();
            XLSX.utils.book_append_sheet(wb, ws, "BT Hizmet Kataloğu");
            XLSX.writeFile(wb, "BT_Hizmet_Kataloğu.xlsx");
        } catch (err) {
            alert("Excel dışa aktarılırken hata oluştu: " + err.message);
            console.error(err);
        }
    }
};

document.addEventListener("DOMContentLoaded", () => App.init());
</script>
</body>
</html>


Kaynaklar:
* ChatGPT
* Gemini