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>
<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) --- */
.sticky-container {
position: sticky;
top: 20px;
z-index: 1000;
margin-bottom: 20px;
}
.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;
}
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 --- */
.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;
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); }
.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);
}
/* --- VERİ DİLİMLEYİCİ VE BİLEŞEN STİLLERİ --- */
.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;
flex-wrap: wrap;
}
.slicer-header-bar span.slicer-title-label {
font-weight: 800;
color: #0056b3;
font-size: 14px;
}
.slicer-input-wrapper {
display: flex;
align-items: center;
gap: 10px;
}
.slicer-input {
padding: 8px 14px;
border: 1px solid #cce5ff;
border-radius: 6px;
font-size: 13px;
outline: none;
width: 280px;
background: #ffffff;
transition: all 0.2s ease;
}
.slicer-input:focus {
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.15);
}
.slicer-input::placeholder {
color: #adb5bd;
font-style: italic;
}
.slicer-area-rows {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 15px;
}
.slicer-row {
display: flex;
align-items: flex-start;
background: #ffffff;
border: 1px solid #e1e4e8;
border-radius: 10px;
padding: 15px 18px;
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
gap: 20px;
}
/* YENİ BAŞLIK VE İSTATİSTİK BÖLÜMÜ GÖRÜNÜMÜ */
.slicer-row-header {
min-width: 250px;
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 6px;
border-right: 2px solid #f0f2f5;
padding-right: 15px;
margin-top: 2px;
}
.slicer-row-title {
font-size: 16px;
font-weight: 800;
color: #1a1e23;
letter-spacing: -0.2px;
}
.slicer-row-stats {
font-size: 13px;
color: #6a737d;
display: flex;
gap: 4px;
align-items: center;
flex-wrap: wrap; /* Daralırsa alta inmesi için */
}
.slicer-stat-text {
color: #586069;
}
.slicer-stat-separator {
color: #d1d5da;
margin: 0 2px;
}
/* GÖRSELE UYGUN KALDIR BUTONU (Kırmızı Metin) */
.slicer-remove-text-btn {
background: none;
border: none;
color: #f43f5e; /* Görseldeki kırmızı/pembe ton */
font-size: 13px;
font-weight: 700;
cursor: pointer;
padding: 0;
margin: 0;
transition: all 0.2s ease;
}
.slicer-remove-text-btn:hover {
color: #e11d48;
text-decoration: underline;
}
/* ÇOK SATIRLI (WRAP) BUTON ALANI */
.slicer-row-buttons {
display: flex;
flex-wrap: wrap; /* Butonlar sığmadığında alt satıra geçer */
gap: 8px;
align-items: center;
flex-grow: 1;
}
/* DİLİMLEYİCİ MODERN SIYAH BUTONLARI */
.glass-btn {
padding: 7px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
display: inline-flex;
align-items: center;
gap: 6px;
outline: none;
white-space: nowrap;
flex-shrink: 0;
}
/* Modern Siyah (İçinde veri olan aktif butonlar) */
.glass-btn.active-glass {
background: linear-gradient(135deg, #1f242d, #11141a);
color: #ffffff;
border: 1px solid #333a48;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
.glass-btn.active-glass:hover {
transform: translateY(-2px);
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.2);
border-color: #00c6ff;
}
/* Seçili Durum Vurgusu (Mavi Cam Vurgulu Modern Siyah) */
.glass-btn.selected-glass {
background: linear-gradient(135deg, #0f172a, #020617) !important;
color: #38bdf8 !important;
border: 1px solid #38bdf8 !important;
box-shadow: 0 0 12px rgba(56, 189, 248, 0.4), inset 0 1px 0 rgba(56, 189, 248, 0.2) !important;
transform: translateY(-1px);
}
/* Pasif (İçinde veri olmayan) Açık Mat Butonlar */
.glass-btn.passive-glass {
background: #f1f5f9;
color: #94a3b8;
border: 1px solid #e2e8f0;
cursor: pointer;
opacity: 0.6;
box-shadow: none;
}
.glass-btn.passive-glass:hover {
background: #e2e8f0;
color: #64748b;
}
/* --- TABLO VE SÜTUN HARFLERİ 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);
}
/* TABLO BAŞLIKLARININ ÜSTÜNDEKİ SÜTUN HARFLERİ (A, B, C...) */
thead th.col-letter-header {
background: #f8f9fc !important;
color: #8c939d !important;
font-weight: 800;
font-size: 11px;
text-align: center !important;
padding: 5px 12px;
letter-spacing: 0.5px;
border-bottom: 1px solid #e1e4e8;
user-select: none;
}
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; }
.col-letter-row { display: none !important; }
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 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>
<div class="sticky-container">
<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">Kullanıcı Destek 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">TEKNİK İŞLETME DAİRESİ BAŞKANLIĞI</div>
</div>
<div class="controls no-print">
<input type="file" id="excelFile" accept=".xls,.xlsx">
<progress id="progressBar" value="0" max="100"></progress>
<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>
<div id="sheetTabsContainer" class="sheet-tabs-container" style="display: none;"></div>
</div>
</div> <div class="slicer-container no-print">
<div class="slicer-header-bar">
<span class="slicer-title-label">🎛️ Veri Dilimleyici Oluştur:</span>
<div class="slicer-input-wrapper">
<input type="text" id="columnInput" class="slicer-input" placeholder="Sütun bilgisi gir (Örn: B, E, F, G)">
<button class="btn-primary" id="btnCreateSlicers">Buton Oluştur</button>
</div>
<div style="margin-left: auto; display: flex; flex-direction: column; align-items: flex-end;">
<button class="btn-danger" id="btnClearSlicers">✖ Filtreleri Sıfırla</button>
<span style="font-size: 10px; color: #6c757d; 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-rows"></div>
</div>
<div class="table-container" id="tableContainer">
<table id="dataTable">
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<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 = "";
// DİLİMLEYİCİ DURUM YÖNETİMİ
let activeSlicerCols = [];
let slicerSelections = {};
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;
activeSlicerCols = [];
slicerSelections = {};
document.getElementById('slicerArea').innerHTML = '';
document.getElementById('columnInput').value = '';
renderHeaders();
currentFilteredData = excelData;
renderPagination();
updateGauges(excelData.length, excelData.length);
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 = '';
// 1. ÜST SATIR: AÇIK GRİ BÜYÜK HARFLER SATIRI (A, B, C, D...)
let letterRow = document.createElement('tr');
letterRow.className = 'col-letter-row no-print';
// 2. ALT SATIR: ANA BAŞLIKLAR SATIRI
let headerRow = document.createElement('tr');
headerKeys.forEach(k => {
let letterTh = document.createElement('th');
letterTh.className = 'col-letter-header';
letterTh.textContent = String(k).toUpperCase(); // A, B, C...
letterRow.appendChild(letterTh);
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(letterRow);
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 String(str !== undefined ? 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`);
}
/* --- VERİ DİLİMLEYİCİ (SLICER) BUTON OLUŞTURMA VE FİLTRELEME MANTIĞI --- */
document.getElementById('btnCreateSlicers').addEventListener('click', function() {
const inputVal = document.getElementById('columnInput').value.trim();
if (!inputVal) {
alert("Lütfen en az bir sütun harfi giriniz (Örn: B, E, F, G)");
return;
}
if (!excelData || excelData.length === 0) {
alert("Lütfen önce bir Excel dosyası yükleyin.");
return;
}
const rawCols = inputVal.split(',').map(c => c.trim().toUpperCase()).filter(c => c !== "");
let validCols = [];
rawCols.forEach(c => {
if (headerKeys.includes(c)) {
if (!validCols.includes(c)) validCols.push(c);
}
});
if (validCols.length === 0) {
alert("Girilen sütun harfleri Excel tablosunda bulunamadı! Lütfen geçerli sütun harfleri giriniz (Örn: A, B, C...).");
return;
}
activeSlicerCols = validCols;
slicerSelections = {};
validCols.forEach(col => {
slicerSelections[col] = new Set();
});
renderSlicerStructure();
applyFiltersAndSlicers();
});
function renderSlicerStructure() {
const container = document.getElementById('slicerArea');
container.innerHTML = '';
activeSlicerCols.forEach(colKey => {
const headerName = actualHeaders[colKey] || 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 rowDiv = document.createElement('div');
rowDiv.className = 'slicer-row';
rowDiv.id = 'slicer_row_' + colKey;
const headerDiv = document.createElement('div');
headerDiv.className = 'slicer-row-header';
// GÖRSELE UYGUN YENİ METİN VE AYIRICI (|) TASARIMI
headerDiv.innerHTML = `
<div class="slicer-row-title">${escapeHTML(headerName)}</div>
<div class="slicer-row-stats" id="stats_${colKey}">
<span class="slicer-stat-text">Toplam: ${sortedVals.length}</span> <span class="slicer-stat-separator">|</span>
<span class="slicer-stat-text">Seçilen: 0</span> <span class="slicer-stat-separator">|</span>
<span class="slicer-stat-text">Aktif: ${sortedVals.length}</span> <span class="slicer-stat-separator">|</span>
<button type="button" class="slicer-remove-text-btn" onclick="removeSlicerRow('${colKey}')" title="Bu dilimleyiciyi kaldır">Kaldır</button>
</div>
`;
const buttonsDiv = document.createElement('div');
buttonsDiv.className = 'slicer-row-buttons';
buttonsDiv.id = 'buttons_' + colKey;
sortedVals.forEach(val => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'glass-btn active-glass';
btn.dataset.col = colKey;
btn.dataset.val = val;
btn.innerHTML = val === "" ? "<i>(Boş Kayıtlar)</i>" : escapeHTML(val);
btn.addEventListener('click', function() {
toggleSlicerValue(colKey, val);
});
buttonsDiv.appendChild(btn);
});
rowDiv.appendChild(headerDiv);
rowDiv.appendChild(buttonsDiv);
container.appendChild(rowDiv);
});
}
// TEK BİR SÜTUN DİLİMLEYİCİSİNİ KALDIRMA FONKSİYONU
window.removeSlicerRow = function(colKey) {
activeSlicerCols = activeSlicerCols.filter(c => c !== colKey);
delete slicerSelections[colKey];
const row = document.getElementById('slicer_row_' + colKey);
if (row) row.remove();
applyFiltersAndSlicers();
};
function toggleSlicerValue(colKey, val) {
if (!slicerSelections[colKey]) {
slicerSelections[colKey] = new Set();
}
const set = slicerSelections[colKey];
if (set.has(val)) {
set.delete(val);
} else {
set.add(val);
}
applyFiltersAndSlicers();
}
document.getElementById('btnClearSlicers').addEventListener('click', function() {
document.getElementById('columnInput').value = '';
activeSlicerCols = [];
slicerSelections = {};
document.getElementById('slicerArea').innerHTML = '';
document.getElementById('globalSearch').value = '';
applyFiltersAndSlicers();
});
window.applyFiltersAndSlicers = function() {
const globalSearchVal = document.getElementById('globalSearch').value.trim().toLocaleLowerCase('tr-TR');
let dataToProcess = excelData.filter(row => {
for (let col of activeSlicerCols) {
const selectedSet = slicerSelections[col];
if (selectedSet && selectedSet.size > 0) {
let cellVal = row[col] !== undefined ? String(row[col]).trim() : "";
if (!selectedSet.has(cellVal)) return false;
}
}
if (globalSearchVal !== "") {
let passesSearch = headerKeys.some(key => {
let cellVal = String(row[key] || '').toLocaleLowerCase('tr-TR');
return cellVal.includes(globalSearchVal);
});
if (!passesSearch) return false;
}
return true;
});
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;
updateSlicerRowsUI(globalSearchVal);
renderPagination();
updateGauges(excelData.length, dataToProcess.length);
};
function updateSlicerRowsUI(globalSearchVal) {
activeSlicerCols.forEach(colKey => {
let subsetForCol = excelData.filter(row => {
for (let otherCol of activeSlicerCols) {
if (otherCol === colKey) continue;
const set = slicerSelections[otherCol];
if (set && set.size > 0) {
let cellVal = row[otherCol] !== undefined ? String(row[otherCol]).trim() : "";
if (!set.has(cellVal)) return false;
}
}
if (globalSearchVal !== "") {
return headerKeys.some(key => {
let cellVal = String(row[key] || '').toLocaleLowerCase('tr-TR');
return cellVal.includes(globalSearchVal);
});
}
return true;
});
let validVals = new Set();
subsetForCol.forEach(row => {
let val = row[colKey] !== undefined ? String(row[colKey]).trim() : "";
validVals.add(val);
});
const selectedSet = slicerSelections[colKey] || new Set();
const statsElem = document.getElementById('stats_' + colKey);
const buttonsElem = document.getElementById('buttons_' + colKey);
if (buttonsElem) {
const buttons = buttonsElem.querySelectorAll('.glass-btn');
const totalCount = buttons.length;
const selectedCount = selectedSet.size;
const activeCount = validVals.size;
if (statsElem) {
// GÖRSELE UYGUN GÜNCELLEME
statsElem.innerHTML = `
<span class="slicer-stat-text">Toplam: ${totalCount}</span> <span class="slicer-stat-separator">|</span>
<span class="slicer-stat-text">Seçilen: ${selectedCount}</span> <span class="slicer-stat-separator">|</span>
<span class="slicer-stat-text">Aktif: ${activeCount}</span> <span class="slicer-stat-separator">|</span>
<button type="button" class="slicer-remove-text-btn" onclick="removeSlicerRow('${colKey}')" title="Bu dilimleyiciyi kaldır">Kaldır</button>
`;
}
buttons.forEach(btn => {
const val = btn.dataset.val;
const isSelected = selectedSet.has(val);
const isActive = validVals.has(val);
btn.className = 'glass-btn';
if (isSelected) {
btn.classList.add('selected-glass');
} else if (isActive) {
btn.classList.add('active-glass');
} else {
btn.classList.add('passive-glass');
}
});
}
});
}
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>



