Raporlama ve Analiz. Bu iki kavram etkin rapor sunmayla varlığını sürdürmektedir. Bu aşamada ise en önemli kısım hızdır. Farklı listeler üzerinde analizler söz konusu olduğunda, elimde bir tool olsa oradan hızlıca filtreleyip hızlıca sonuca varabilsem düşüncesinin bir sonucu olarak ortaya çıkan bir ürün.
HTML kod olarak hazırlanan çalışma, sayfa sonunda yer almaktadır. Bir not defteri açıp kodu not alanına yapıştırın. Dosya ismi verip, uzantıyı ".html" olarak kaydetmeniz yeterli. sonrasında, çift tıklayarak çalıştırın.
Aşağıda uygulamanın çalışmış hali görünmektedir. Helpdesk biriminin Envanterini görüntüleyip analiz etmek istiyoruz. Sayfanın sonunda bir logo yer almakta. Kurumunuzun logosunu "logo.jpg" dosya ismiyle html dosyasının bulunduğu alanda yer alması ile logo belirir. "Envanter, Helpdesk Koordinatörlüğü, MYO MÜDÜRLÜĞÜ" ifadelerini, Kod üzerinde Not defteri ile açıp "Ctrl+F" ile bulup değiştirip kaydetmek suretiyle, kendinize göre uyarlayabilirsiniz.
Başlık alanında orta kısımda, tablodaki toplam satır sayısı, filtre durumunda bulunan sonuca göre oranı ve bulunan filtre adeti bu alana yansımaktadır. dosya seç ile Excel dosyanızı içeri aktarabilirsiniz. (Excel dosyasının tablosu köşelere yanaşık ve boşluk olmadan yer almalı) Filtreleme sonrası, Filtrelenen Excel dosyasını indirebilir ve PDF olarak kaydedebilir yazdırabilirsiniz.
Başlıklara göre, Veri dilimleyici ekleyebilir (Filtre içinde filtre seçimi ile) hızla sonuca ulaşabilir. "Tüm Filtreleri Temizle" butonuna basarak filtreleri kaldırabilirsiniz. Başlıklara tıklayarak (A-Z veya Z-A) sıralama yapabilir, her başlık üzerindeki arama penceresini kullanarak arama yapabilirsiniz.
Helpdesk için için hazırlanan helpdesk datası.
"Dosya seç" butonuna basarak, Excel datanızı içeri aktarın.
"Veri Dilimleyici Ekle" butonuna barak, filtre başlıkları ekleyebilirsiniz. (İhtiyacınız olacak her başlık için, seçim yapıp "Ekle" butonuna basınız.
Eklenen dilimleyici başlıklarında ayrıca, bağımsız değişkenlerin adet bilgileri (toplam değeri) yer alır.
Seçilen filtreye göre, anlık olarak tablo filtrelenir. seçilen filtredeki satır sayısı ve Toplama göre oranı, grafiğe yansır.
Birden fazla filtre seçebilirsiniz. (filtre içinde filtre) seçilen filtrenin alt kümesi olmayan (Filtrelerde yer almayan diğer seçimler) açık gri olarak filtrede görünür.
Adetli seçimler, ihtiyacınız hızlıca ulaşmanızı sağlar.
Başlıklar üzerine basarak liste üzerinde (A-Z veya Z-A) sıralama yapabilirsiniz.
iste üzerinde (A-Z veya Z-A) sıralama yapabilirsiniz.
Yazdırma ön izlemesi yapabilir, PDF olarak kaydedebilir, yazdırabilirsiniz.
Filtrelediğiniz Veriyi, Excel olarak indirebilirsiniz.
İndirdiğiz Excel görünümü.
İndirdiğiniz Exceli, dosya seç ile yeniden içeri aktararak, toplam adet bilgisine göre değerlendirebileceğiniz filtreleyip raporlayabileceğiniz özeldeki dataya erişerek paylaşabilirsiniz.
Yukarıda anlatıldığı gibi aynı şekilde dilimleyici ekleyerek filtreleyebilirsiniz.
.png)
Başlıklarda ayrıca filtreleme yapabilirsiniz.
.png)
PDF olarak kaydedebilir, Yazdırabilirsiniz.
Formun Geliştirilmiş Son hali ve Kod aşağıdadır (Tüm Tablolarda Arama Özelliği)
Son güncelleme; Excel sekmelerini de görebilme, Arama temizleme, Sayfa yenileme. Güncel kod aşağıdadır.
HTML Kod:
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Envanter Sistemi</title>
<!-- SheetJS kütüphanesi - Excel okuma ve yazma için -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
/* --- TEMEL DEĞİŞKENLER VE SIFIRLAMALAR --- */
:root {
--bg: #f0f2f5;
--shadow: 0 8px 24px rgba(0,0,0,.05);
--radius: 10px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", system-ui, Arial, sans-serif;
}
body {
background: var(--bg);
padding: 20px;
}
/* --- ORTAK SABİT ALAN (BAŞLIK + ARAMA) --- */
/* Başlık ve Arama kutusunu bir bütün olarak yukarıya sabitler */
.sticky-container {
position: sticky;
top: 20px;
z-index: 1000;
margin-bottom: 20px;
}
/* Kaydırılan tablo verilerinin aradaki boşluktan görünmesini engellemek için arka plan dolgusu */
.sticky-container::before {
content: "";
position: absolute;
top: -20px;
left: -20px;
right: -20px;
bottom: -10px;
background: var(--bg);
z-index: -1;
}
/* --- BAŞLIK ALANI STİLLERİ --- */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: #fff;
border: 1px solid #e1e4e8;
border-radius: var(--radius);
box-shadow: 0 4px 20px rgba(0,0,0,0.06);
margin-bottom: 15px;
gap: 15px;
}
.header .left {
display: flex;
align-items: center;
gap: 15px;
width: 30%;
}
.header .logo img {
height: 50px;
}
.header .title1 {
font-size: 22px;
font-weight: 800;
color: #1a1e23;
letter-spacing: -0.5px;
}
.header .title2 {
font-size: 12px;
color: #6a737d;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* --- ESTETİK VE TEKNOLOJİK GRAFİK BÖLÜMÜ (HUD STİLİ) --- */
.header .center-title {
flex-grow: 1;
display: flex;
justify-content: center;
}
.gauges-wrapper {
display: flex;
gap: 30px;
background: #fafbfc;
border: 1px solid #eef0f3;
padding: 12px 30px;
border-radius: 50px;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.02), 0 4px 10px rgba(0,0,0,0.03);
}
.tech-gauge {
display: flex;
flex-direction: column;
align-items: center;
width: 100px;
position: relative;
}
.tg-label {
font-size: 11px;
font-weight: 800;
color: #586069;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: -2px;
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
.tg-label.total::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: #00c6ff; box-shadow: 0 0 6px #00c6ff; }
.tg-label.percent::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: #ff416c; box-shadow: 0 0 6px #ff416c; }
.tg-label.filtered::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: #11998e; box-shadow: 0 0 6px #11998e; }
.tech-gauge svg {
width: 100%;
overflow: visible;
filter: drop-shadow(0px 4px 4px rgba(0,0,0,0.1));
}
.tg-value {
font-size: 22px;
font-weight: 900;
fill: #24292e;
font-family: "Courier New", Courier, monospace;
}
.header .right {
width: 30%;
font-size: 13px;
font-weight: 800;
text-align: right;
color: #24292e;
letter-spacing: 0.5px;
}
/* --- KONTROL & DOSYA YÜKLEME PANELİ --- */
.controls {
display: flex;
gap: 15px;
align-items: center;
background: #fff;
padding: 15px;
border-radius: var(--radius);
box-shadow: var(--shadow);
border-left: 5px solid #00c6ff;
flex-wrap: wrap; /* Sekmeler için sığmasını sağlar */
}
input[type="file"], button, select {
padding: 8px 15px;
border: 1px solid #d1d5da;
border-radius: 6px;
font-size: 13px;
outline: none;
}
input[type="file"]:focus, select:focus { border-color: #00c6ff; }
/* --- GENEL ARAMA (GLOBAL SEARCH) ALANI - DAİMİ ÇERÇEVELİ --- */
.search-container {
display: flex;
flex-grow: 1;
align-items: center;
gap: 10px;
margin: 0 15px;
}
#globalSearch {
flex-grow: 1;
margin: 0;
padding: 10px 15px;
border-radius: 6px;
font-size: 14px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
/* Tıklanmış gibi daima aktif renklerde */
border: 1px solid #00c6ff;
box-shadow: 0 0 0 3px rgba(0, 198, 255, 0.15);
}
#globalSearch::placeholder {
color: #b0b3b8;
}
.btn-primary {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white; border: none; cursor: pointer; font-weight: bold; transition: 0.3s;
box-shadow: 0 4px 10px rgba(0, 123, 255, 0.3);
}
.btn-primary:hover { box-shadow: 0 6px 15px rgba(0, 123, 255, 0.5); transform: translateY(-1px); }
.btn-danger {
background: linear-gradient(135deg, #dc3545, #a71d2a);
color: white; border: none; cursor: pointer; font-weight: bold; transition: 0.3s;
box-shadow: 0 4px 10px rgba(220, 53, 69, 0.3);
}
.btn-danger:hover { box-shadow: 0 6px 15px rgba(220, 53, 69, 0.5); transform: translateY(-1px); }
.btn-success {
background: linear-gradient(135deg, #28a745, #218838);
color: white; border: none; cursor: pointer; font-weight: bold; transition: 0.3s;
box-shadow: 0 4px 10px rgba(40, 167, 69, 0.3);
}
.btn-success:hover { box-shadow: 0 6px 15px rgba(40, 167, 69, 0.5); transform: translateY(-1px); }
/* Yeni Sayfa Yenile Butonu Stili */
.btn-secondary {
background: linear-gradient(135deg, #e1e4e8, #d1d5da);
color: #24292e; border: none; cursor: pointer; font-weight: bold; transition: 0.3s;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.btn-secondary:hover { box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); transform: translateY(-1px); }
progress { width: 150px; height: 18px; display: none; border-radius: 10px; }
progress::-webkit-progress-bar { background-color: #f0f2f5; border-radius: 10px; }
progress::-webkit-progress-value { background: linear-gradient(90deg, #00c6ff, #0072ff); border-radius: 10px; }
/* --- EXCEL SEKMELERİ (TABS) STİLLERİ --- */
.sheet-tabs-container {
width: 100%;
display: flex;
gap: 8px;
margin-top: 10px;
overflow-x: auto;
padding-bottom: 5px;
}
.sheet-tab-btn {
background: #f8f9fc;
color: #007bff;
border: 1px solid #cce5ff;
padding: 6px 16px;
border-radius: 20px;
cursor: pointer;
font-size: 13px;
font-weight: 700;
transition: all 0.3s ease;
white-space: nowrap;
}
.sheet-tab-btn:hover {
background: #e6f2ff;
border-color: #007bff;
}
.sheet-tab-btn.active {
background: linear-gradient(135deg, #007bff, #0056b3);
color: #fff;
border-color: transparent;
box-shadow: 0 4px 10px rgba(0, 123, 255, 0.3);
}
/* --- EXCEL DİLİMLEYİCİ (SLICER) ALANI --- */
.slicer-container {
background: #f8f9fc;
padding: 20px;
border-radius: var(--radius);
box-shadow: var(--shadow);
margin-bottom: 20px;
border: 1px solid #e1e4e8;
}
.slicer-header-bar {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.slicer-header-bar span { font-weight: 800; color: #0056b3; font-size: 14px; }
select#slicerColSelect { min-width: 200px; }
.slicer-area { display: flex; flex-wrap: wrap; gap: 15px; }
.slicer-box {
border: 1px solid #d1d5da;
border-radius: 8px;
background: #fff;
width: 220px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 4px 10px rgba(0,0,0,0.04);
}
.slicer-box-header {
background: #24292e;
color: white;
padding: 10px 12px;
font-size: 13px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.slicer-box-close {
cursor: pointer; color: #ff6b6b; font-weight: bold; border: none; background: transparent; font-size: 15px;
}
.slicer-item-list {
max-height: 180px; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 5px;
}
.slicer-item {
display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px; cursor: pointer; border-radius: 4px; transition: 0.2s;
}
.slicer-item:hover { background: #f0f2f5; }
.slicer-item.disabled-slicer { color: #adb5bd; }
.slicer-item.disabled-slicer input { opacity: 0.4; }
/* --- TABLO VE BAŞLIK STİLLERİ --- */
.table-container {
width: 100%;
max-height: 65vh;
overflow: auto;
background: #fff;
border-radius: var(--radius);
box-shadow: var(--shadow);
position: relative;
}
table { width: max-content; min-width: 100%; border-collapse: separate; border-spacing: 0; }
th, td { border-bottom: 1px solid #e1e4e8; border-right: 1px solid #e1e4e8; padding: 12px; text-align: left; font-size: 13px; white-space: nowrap; }
th:first-child, td:first-child { border-left: 1px solid #e1e4e8; }
thead {
position: sticky;
top: 0;
z-index: 20;
background: #fff;
box-shadow: 0 4px 6px rgba(0,0,0,0.06);
}
thead th.main-header {
background: #1a1e23 !important;
color: white !important;
font-weight: bold;
border-top: 1px solid #1a1e23;
cursor: pointer;
user-select: none;
transition: background 0.2s;
}
thead th.main-header:hover {
background: #2c3238 !important;
}
.sort-icon {
font-size: 11px;
margin-left: 5px;
color: #00c6ff;
display: inline-block;
width: 12px;
}
tbody tr:nth-child(even) { background-color: #f8f9fc; }
tbody tr:hover { background-color: #e6f2ff; }
/* --- ARAMA VURGUSU (HIGHLIGHT) STİLİ --- */
.highlight-match {
background-color: #d4edda;
color: #155724;
font-weight: bold;
border-radius: 3px;
padding: 1px 2px;
}
/* --- SAYFALAMA (PAGINATION) STİLLERİ --- */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #fff;
border-radius: var(--radius);
box-shadow: var(--shadow);
margin-top: 15px;
}
.pagination-controls button { margin: 0 5px; }
/* --- YAZDIRMA (PRINT) VE PDF STİLLERİ --- */
@media print {
.no-print { display: none !important; }
body { background: white; padding: 0; margin: 0; }
.sticky-container { position: static !important; }
.sticky-container::before { content: none !important; }
.header { box-shadow: none; border-color: #000; margin-bottom: 20px; position: static; }
.table-container { box-shadow: none; border-radius: 0; overflow: visible; max-height: none; }
table { width: 100%; max-width: 100%; font-size: 10px; }
th, td { white-space: nowrap !important; padding: 6px; }
thead { position: static; box-shadow: none; }
thead th.main-header { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
.highlight-match {
background-color: transparent !important;
color: inherit !important;
font-weight: normal !important;
padding: 0 !important;
}
@page { size: landscape; margin: 10mm; @bottom-right { content: "Sayfa " counter(page) " / " counter(pages); font-size: 10px; } }
}
</style>
</head>
<body>
<!-- SVG GRADYAN TANIMLAMALARI -->
<svg style="width:0; height:0; position:absolute;" aria-hidden="true" focusable="false">
<defs>
<linearGradient id="gradTotal" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#00c6ff" />
<stop offset="100%" stop-color="#0072ff" />
</linearGradient>
<linearGradient id="gradPercent" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#ff416c" />
<stop offset="100%" stop-color="#ff4b2b" />
</linearGradient>
<linearGradient id="gradFiltered" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#11998e" />
<stop offset="100%" stop-color="#38ef7d" />
</linearGradient>
</defs>
</svg>
<!-- SABİT ALAN: BAŞLIK VE KONTROLLER BURADA BİRLEŞTİRİLDİ -->
<div class="sticky-container">
<!-- BAŞLIK ALANI BAŞLANGICI -->
<div class="header">
<div class="left">
<div class="logo"><img src="logo.jpg" alt="Logo" onerror="this.style.display='none'"></div>
<div>
<div class="title1">Envanter</div>
<div class="title2">Helpdesk Koordinatörlüğü</div>
</div>
</div>
<div class="center-title">
<div class="gauges-wrapper">
<div class="tech-gauge">
<div class="tg-label total">Adet</div>
<svg viewBox="0 0 100 55">
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="#e1e4e8" stroke-width="14" stroke-linecap="round" />
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="#ffffff" stroke-width="14" stroke-dasharray="2 4" />
<path id="pathTotal" d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="url(#gradTotal)" stroke-width="10" stroke-linecap="round"
style="stroke-dasharray: 125.6; stroke-dashoffset: 0; transition: stroke-dashoffset 1s ease-out;" />
<text x="50" y="47" text-anchor="middle" class="tg-value" id="gTotal">0</text>
</svg>
</div>
<div class="tech-gauge">
<div class="tg-label percent">Oran</div>
<svg viewBox="0 0 100 55">
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="#e1e4e8" stroke-width="14" stroke-linecap="round" />
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="#ffffff" stroke-width="14" stroke-dasharray="2 4" />
<path id="pathPercent" d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="url(#gradPercent)" stroke-width="10" stroke-linecap="round"
style="stroke-dasharray: 125.6; stroke-dashoffset: 125.6; transition: stroke-dashoffset 1s ease-out;" />
<text x="50" y="47" text-anchor="middle" class="tg-value" id="gPercent">%0</text>
</svg>
</div>
<div class="tech-gauge">
<div class="tg-label filtered">Filtre Adet</div>
<svg viewBox="0 0 100 55">
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="#e1e4e8" stroke-width="14" stroke-linecap="round" />
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="#ffffff" stroke-width="14" stroke-dasharray="2 4" />
<path id="pathFiltered" d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="url(#gradFiltered)" stroke-width="10" stroke-linecap="round"
style="stroke-dasharray: 125.6; stroke-dashoffset: 125.6; transition: stroke-dashoffset 1s ease-out;" />
<text x="50" y="47" text-anchor="middle" class="tg-value" id="gFiltered">0</text>
</svg>
</div>
</div>
</div>
<div class="right">MYO MÜDÜRLÜĞÜ</div>
</div>
<!-- DOSYA YÜKLEME VE YAZDIRMA -->
<div class="controls no-print">
<input type="file" id="excelFile" accept=".xls,.xlsx">
<progress id="progressBar" value="0" max="100"></progress>
<!-- GENEL ARAMA ALANI (GÜNCELLENDİ) -->
<div class="search-container no-print">
<button class="btn-secondary" type="button" onclick="window.location.reload();">Sayfa Yenile</button>
<button class="btn-danger" type="button" onclick="document.getElementById('globalSearch').value=''; applyFiltersAndSlicers();">Ara Temizle</button>
<input type="text" id="globalSearch" placeholder="Tüm başlıklarda, tek yerde Ara..." oninput="applyFiltersAndSlicers()">
</div>
<div style="margin-left: auto; display: flex; gap: 10px;">
<button class="btn-success" type="button" onclick="exportToExcel()">📥 Excel İndir</button>
<button class="btn-primary" type="button" onclick="window.print()">🖨️ PDF / Yazdır</button>
</div>
<!-- SEKME LİSTESİ ALANI (Sadece birden fazla sekme varsa görünür) -->
<div id="sheetTabsContainer" class="sheet-tabs-container" style="display: none;"></div>
</div>
</div> <!-- .sticky-container BİTİŞİ -->
<!-- EXCEL DİLİMLEYİCİ (SLICER) ALANI -->
<div class="slicer-container no-print">
<div class="slicer-header-bar">
<span>🎛️ Veri Dilimleyici Ekle:</span>
<select id="slicerColSelect">
<option value="">-- Önce Excel Yükleyin --</option>
</select>
<button class="btn-primary" id="btnAddSlicer">+ Ekle</button>
<div style="margin-left: auto; display: flex; flex-direction: column; align-items: flex-end;">
<button class="btn-danger" id="btnClearSlicers">✖ Tüm Filtreleri Temizle</button>
<span style="font-size: 10px; color: #ced4da; margin-top: 5px; font-weight: 600; letter-spacing: 0.5px;">AI Web Designer: Muhammed Yusuf OLGUN</span>
</div>
</div>
<div id="slicerArea" class="slicer-area"></div>
</div>
<!-- TABLO ALANI -->
<div class="table-container" id="tableContainer">
<table id="dataTable">
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- SAYFALAMA KONTROLLERİ -->
<div class="pagination-container no-print" id="paginationContainer" style="display:none;">
<div id="pageInfo" style="font-size: 13px; font-weight: bold; color: #586069;"></div>
<div class="pagination-controls">
<button class="btn-primary" onclick="changePage(-1)">Önceki</button>
<span id="pageIndicator" style="margin: 0 10px; font-weight: bold; color: #24292e;"></span>
<button class="btn-primary" onclick="changePage(1)">Sonraki</button>
</div>
<div>
<select id="rowsPerPage" onchange="changeRowsPerPage()">
<option value="50">Sayfada 50 Kayıt</option>
<option value="100" selected>Sayfada 100 Kayıt</option>
<option value="500">Sayfada 500 Kayıt</option>
<option value="1000">Sayfada 1000 Kayıt</option>
</select>
</div>
</div>
<script>
let excelData = [];
let actualHeaders = {};
let headerKeys = [];
let currentFilteredData = [];
let currentPage = 1;
let rowsPerPage = 100;
let currentSortCol = null;
let sortAsc = true;
const PATH_LENGTH = 125.6;
let currentWorkbook = null;
let currentSheetName = "";
document.getElementById('excelFile').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const pb = document.getElementById('progressBar');
pb.style.display = 'block';
pb.value = 10;
const reader = new FileReader();
reader.onload = function(event) {
pb.value = 40;
try {
const data = new Uint8Array(event.target.result);
currentWorkbook = XLSX.read(data, {type: 'array'});
pb.value = 60;
const sheetNames = currentWorkbook.SheetNames;
renderSheetTabs(sheetNames);
loadSheetData(sheetNames[0]);
pb.value = 100;
setTimeout(() => { pb.style.display = 'none'; }, 500);
} catch (error) {
alert("Dosya okunurken bir hata oluştu: " + error.message);
pb.style.display = 'none';
}
};
reader.readAsArrayBuffer(file);
});
function renderSheetTabs(sheetNames) {
const container = document.getElementById('sheetTabsContainer');
if (sheetNames.length <= 1) {
container.style.display = 'none';
container.innerHTML = '';
return;
}
container.style.display = 'flex';
container.innerHTML = '';
sheetNames.forEach(name => {
const btn = document.createElement('button');
btn.className = 'sheet-tab-btn';
btn.innerText = name;
btn.onclick = () => {
const pb = document.getElementById('progressBar');
pb.style.display = 'block';
pb.value = 50;
setTimeout(() => {
loadSheetData(name);
pb.style.display = 'none';
}, 50);
};
container.appendChild(btn);
});
}
function loadSheetData(sheetName) {
currentSheetName = sheetName;
const container = document.getElementById('sheetTabsContainer');
if(container.style.display !== 'none') {
const btns = container.querySelectorAll('.sheet-tab-btn');
btns.forEach(btn => {
if (btn.innerText === sheetName) btn.classList.add('active');
else btn.classList.remove('active');
});
}
const worksheet = currentWorkbook.Sheets[sheetName];
const rawData = XLSX.utils.sheet_to_json(worksheet, {header: "A", defval: ""});
if (rawData.length > 0) {
actualHeaders = rawData[0];
headerKeys = Object.keys(actualHeaders).filter(key => String(actualHeaders[key]).trim() !== "");
excelData = rawData.slice(1).filter(row => {
return headerKeys.some(key => String(row[key]).trim() !== "");
});
currentSortCol = null;
sortAsc = true;
currentPage = 1;
populateSlicerDropdown();
renderHeaders();
currentFilteredData = excelData;
renderPagination();
updateGauges(excelData.length, excelData.length);
document.getElementById('slicerArea').innerHTML = '';
document.getElementById('globalSearch').value = '';
} else {
alert(`"${sheetName}" sekmesinde veri bulunamadı!`);
excelData = [];
currentFilteredData = [];
renderHeaders();
renderPagination();
updateGauges(0, 0);
}
}
function renderHeaders() {
const thead = document.getElementById('tableHead');
thead.innerHTML = '';
let headerRow = document.createElement('tr');
headerKeys.forEach(k => {
let th = document.createElement('th');
th.className = 'main-header';
th.title = 'Sıralamak için tıklayın';
th.innerHTML = `<span>${actualHeaders[k]}</span><span class="sort-icon" id="sort_icon_${k}"></span>`;
th.addEventListener('click', () => {
if (currentSortCol === k) {
sortAsc = !sortAsc;
} else {
currentSortCol = k;
sortAsc = true;
}
updateSortIcons();
applyFiltersAndSlicers();
});
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
}
function updateSortIcons() {
headerKeys.forEach(k => {
const iconSpan = document.getElementById('sort_icon_' + k);
if (iconSpan) {
if (currentSortCol === k) {
iconSpan.innerHTML = sortAsc ? '▲' : '▼';
} else {
iconSpan.innerHTML = '';
}
}
});
}
function escapeHTML(str) {
return str.replace(/[&<>'"]/g,
tag => ({
'&': '&', '<': '<', '>': '>', "'": ''', '"': '"'
}[tag])
);
}
function getHighlightedHTML(text, searchVal) {
const str = String(text !== undefined ? text : '');
if (!searchVal) return escapeHTML(str);
const lowerText = str.toLocaleLowerCase('tr-TR');
const lowerSearch = searchVal.toLocaleLowerCase('tr-TR');
let startIndex = 0;
let result = '';
let index;
while ((index = lowerText.indexOf(lowerSearch, startIndex)) > -1) {
result += escapeHTML(str.substring(startIndex, index));
result += `<span class="highlight-match">${escapeHTML(str.substring(index, index + searchVal.length))}</span>`;
startIndex = index + searchVal.length;
}
result += escapeHTML(str.substring(startIndex));
return result;
}
function renderPagination() {
const tbody = document.getElementById('tableBody');
tbody.innerHTML = '';
const totalRows = currentFilteredData.length;
if (totalRows === 0) {
document.getElementById('paginationContainer').style.display = 'none';
return;
}
document.getElementById('paginationContainer').style.display = 'flex';
const totalPages = Math.ceil(totalRows / rowsPerPage);
if (currentPage > totalPages) currentPage = totalPages;
if (currentPage < 1) currentPage = 1;
const startIndex = (currentPage - 1) * rowsPerPage;
const endIndex = Math.min(startIndex + rowsPerPage, totalRows);
const pageData = currentFilteredData.slice(startIndex, endIndex);
const searchVal = document.getElementById('globalSearch').value.trim();
pageData.forEach(row => {
let tr = document.createElement('tr');
headerKeys.forEach(k => {
let td = document.createElement('td');
td.innerHTML = getHighlightedHTML(row[k], searchVal);
tr.appendChild(td);
});
tbody.appendChild(tr);
});
document.getElementById('pageInfo').innerText = `Toplam ${totalRows} kayıttan ${startIndex + 1} - ${endIndex} arası gösteriliyor.`;
document.getElementById('pageIndicator').innerText = `Sayfa ${currentPage} / ${totalPages}`;
}
window.changePage = function(dir) {
currentPage += dir;
renderPagination();
document.getElementById('tableContainer').scrollTo({ top: 0, behavior: 'smooth' });
}
window.changeRowsPerPage = function() {
rowsPerPage = parseInt(document.getElementById('rowsPerPage').value);
currentPage = 1;
renderPagination();
document.getElementById('tableContainer').scrollTo({ top: 0, behavior: 'smooth' });
}
window.exportToExcel = function() {
if (!currentFilteredData || currentFilteredData.length === 0) {
alert("Dışa aktarılacak veri bulunamadı.");
return;
}
const exportData = currentFilteredData.map(row => {
let cleanRow = {};
headerKeys.forEach(k => {
cleanRow[actualHeaders[k]] = row[k];
});
return cleanRow;
});
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
let exportSheetName = currentSheetName ? currentSheetName.substring(0, 31) : "Filtrelenmis_Veri";
XLSX.utils.book_append_sheet(wb, ws, exportSheetName);
XLSX.writeFile(wb, `Envanter_Raporu_${exportSheetName}.xlsx`);
}
function populateSlicerDropdown() {
const select = document.getElementById('slicerColSelect');
select.innerHTML = '<option value="">-- Filtrelenecek Sütunu Seçin --</option>';
headerKeys.forEach(k => {
let opt = document.createElement('option');
opt.value = k;
opt.textContent = actualHeaders[k];
select.appendChild(opt);
});
}
document.getElementById('btnAddSlicer').addEventListener('click', function() {
const select = document.getElementById('slicerColSelect');
const colKey = select.value;
if (!colKey) return;
if (document.getElementById('slicer_' + colKey)) {
alert('Bu sütun için dilimleyici zaten açık!');
return;
}
const headerName = actualHeaders[colKey];
let uniqueVals = new Set();
excelData.forEach(row => {
let val = row[colKey] !== undefined ? String(row[colKey]).trim() : "";
uniqueVals.add(val);
});
let sortedVals = Array.from(uniqueVals).sort((a, b) => a.localeCompare(b, 'tr-TR', {numeric: true}));
const slicerArea = document.getElementById('slicerArea');
const box = document.createElement('div');
box.className = 'slicer-box';
box.id = 'slicer_' + colKey;
let listHtml = sortedVals.map(v => `
<label class="slicer-item" data-active="1">
<input type="checkbox" value="${v.replace(/"/g, '"')}" onchange="applyFiltersAndSlicers()">
${v === "" ? "<i>(Boş Kayıtlar)</i>" : v}
</label>
`).join('');
box.innerHTML = `
<div class="slicer-box-header">
<span class="slicer-title" data-name="${headerName}">${headerName} (${sortedVals.length})</span>
<button class="slicer-box-close" onclick="removeSlicer('${colKey}')">✖</button>
</div>
<div class="slicer-item-list">
${listHtml}
</div>
`;
slicerArea.appendChild(box);
select.value = "";
applyFiltersAndSlicers();
});
window.removeSlicer = function(colKey) {
const box = document.getElementById('slicer_' + colKey);
if (box) {
box.remove();
applyFiltersAndSlicers();
}
};
document.getElementById('btnClearSlicers').addEventListener('click', function() {
document.getElementById('slicerArea').innerHTML = '';
document.getElementById('globalSearch').value = '';
applyFiltersAndSlicers();
});
function updateSlicerUIs(slicerConds, globalSearchVal) {
const boxes = document.querySelectorAll('.slicer-box');
boxes.forEach(box => {
const colKey = box.id.replace('slicer_', '');
let otherSlicerConds = slicerConds.filter(cond => cond.col !== colKey);
let subset = excelData;
if (otherSlicerConds.length > 0 || globalSearchVal !== "") {
subset = subset.filter(row => {
let passesSlicer = otherSlicerConds.every(cond => {
let cellVal = row[cond.col] !== undefined ? String(row[cond.col]).trim() : "";
return cond.allowed.includes(cellVal);
});
if (!passesSlicer) return false;
let passesSearch = true;
if (globalSearchVal !== "") {
passesSearch = headerKeys.some(key => {
let cellVal = String(row[key] || '').toLocaleLowerCase('tr-TR');
return cellVal.includes(globalSearchVal);
});
}
return passesSearch;
});
}
let validVals = new Set();
subset.forEach(row => {
let val = row[colKey] !== undefined ? String(row[colKey]).trim() : "";
validVals.add(val);
});
const itemList = box.querySelector('.slicer-item-list');
const labels = Array.from(itemList.querySelectorAll('.slicer-item'));
let activeCount = 0;
labels.forEach(label => {
const input = label.querySelector('input');
const val = input.value;
if (validVals.has(val)) {
label.classList.remove('disabled-slicer');
label.dataset.active = "1";
activeCount++;
} else {
label.classList.add('disabled-slicer');
label.dataset.active = "0";
}
});
const titleSpan = box.querySelector('.slicer-title');
if (titleSpan) {
titleSpan.innerText = `${titleSpan.dataset.name} (${activeCount})`;
}
labels.sort((a, b) => {
const activeA = a.dataset.active;
const activeB = b.dataset.active;
if (activeA !== activeB) {
return activeB.localeCompare(activeA);
}
const valA = a.querySelector('input').value;
const valB = b.querySelector('input').value;
return valA.localeCompare(valB, 'tr-TR', {numeric: true});
});
labels.forEach(label => itemList.appendChild(label));
});
}
window.applyFiltersAndSlicers = function() {
const boxes = document.querySelectorAll('.slicer-box');
let slicerConds = [];
boxes.forEach(box => {
const colKey = box.id.replace('slicer_', '');
const checkedInputs = box.querySelectorAll('input[type="checkbox"]:checked');
if (checkedInputs.length > 0) {
let allowedVals = Array.from(checkedInputs).map(inp => inp.value);
slicerConds.push({ col: colKey, allowed: allowedVals });
}
});
const globalSearchVal = document.getElementById('globalSearch').value.trim().toLocaleLowerCase('tr-TR');
updateSlicerUIs(slicerConds, globalSearchVal);
let dataToProcess = excelData;
if (slicerConds.length > 0 || globalSearchVal !== "") {
dataToProcess = excelData.filter(row => {
let passesSlicer = slicerConds.every(cond => {
let cellVal = row[cond.col] !== undefined ? String(row[cond.col]).trim() : "";
return cond.allowed.includes(cellVal);
});
if (!passesSlicer) return false;
let passesSearch = true;
if (globalSearchVal !== "") {
passesSearch = headerKeys.some(key => {
let cellVal = String(row[key] || '').toLocaleLowerCase('tr-TR');
return cellVal.includes(globalSearchVal);
});
}
return passesSearch;
});
}
if (currentSortCol) {
dataToProcess = [...dataToProcess];
dataToProcess.sort((a, b) => {
let valA = a[currentSortCol] !== undefined ? String(a[currentSortCol]).trim() : "";
let valB = b[currentSortCol] !== undefined ? String(b[currentSortCol]).trim() : "";
let numA = parseFloat(valA.replace(',', '.'));
let numB = parseFloat(valB.replace(',', '.'));
let isNum = !isNaN(numA) && !isNaN(numB) && valA !== "" && valB !== "";
if (isNum) {
return sortAsc ? numA - numB : numB - numA;
} else {
return sortAsc
? valA.localeCompare(valB, 'tr-TR', { numeric: true })
: valB.localeCompare(valA, 'tr-TR', { numeric: true });
}
});
}
currentPage = 1;
currentFilteredData = dataToProcess;
renderPagination();
updateGauges(excelData.length, dataToProcess.length);
};
function updateGauges(total, filtered) {
document.getElementById('gTotal').textContent = total;
let percent = total === 0 ? 0 : Math.round((filtered / total) * 100);
document.getElementById('gPercent').textContent = '%' + percent;
document.getElementById('gFiltered').textContent = filtered;
document.getElementById('pathTotal').style.strokeDashoffset = total > 0 ? 0 : PATH_LENGTH;
let pOffset = PATH_LENGTH - (PATH_LENGTH * percent / 100);
document.getElementById('pathPercent').style.strokeDashoffset = isNaN(pOffset) ? PATH_LENGTH : pOffset;
let fOffset = PATH_LENGTH - (PATH_LENGTH * percent / 100);
document.getElementById('pathFiltered').style.strokeDashoffset = isNaN(fOffset) ? PATH_LENGTH : fOffset;
}
</script>
</body>
</html>
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
