


<!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;
}
/* --- BAŞLIK ALANI STİLLERİ (SABİT) --- */
.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: 25px;
gap: 15px;
position: sticky;
top: 20px;
z-index: 1000;
}
.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;
flex-direction: column;
align-items: center;
gap: 15px;
}
.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);
align-items: center;
}
.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;
}
.profile-photo-box {
width: 360px;
height: 200px;
border-radius: 8px;
background: #fff;
border: 1px solid #eef0f3;
box-shadow: 0 4px 10px rgba(0,0,0,0.03);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.profile-photo-box img {
width: 100%;
height: 100%;
object-fit: contain;
display: none;
}
.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;
flex-direction: column;
gap: 15px;
background: #fff;
padding: 15px;
border-radius: var(--radius);
box-shadow: var(--shadow);
margin-bottom: 15px;
border-left: 5px solid #00c6ff;
position: sticky;
top: 365px;
z-index: 999;
}
.controls-top {
display: flex;
gap: 15px;
align-items: center;
width: 100%;
}
input[type="file"], input[type="text"], button, select {
padding: 8px 15px;
border: 1px solid #d1d5da;
border-radius: 6px;
font-size: 13px;
outline: none;
}
/* DAİMİ ODAKLI GENEL ARAMA ALANI */
#globalSearch {
flex-grow: 1;
margin: 0 10px 0 0;
padding: 10px 15px;
border-radius: 8px;
font-size: 14px;
border: 1px solid #00c6ff !important;
box-shadow: 0 0 0 3px rgba(0, 198, 255, 0.15) !important;
color: #24292e;
transition: 0.3s;
}
#globalSearch::placeholder {
color: #a0aab5;
}
.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, #e9ecef, #dee2e6);
color: #212529; border: 1px solid #ced4da; cursor: pointer; font-weight: bold; transition: 0.3s;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
.btn-secondary:hover { box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); transform: translateY(-1px); background: linear-gradient(135deg, #dee2e6, #ced4da); }
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; }
/* SEKME (SHEET) LİSTESİ STİLLERİ */
#sheetListContainer {
display: none;
gap: 10px;
flex-wrap: wrap;
padding-top: 10px;
border-top: 1px dashed #e1e4e8;
width: 100%;
}
.sheet-btn {
padding: 6px 15px;
border: 1px solid #00c6ff;
background: #fff;
color: #007bff;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: bold;
transition: 0.3s;
}
.sheet-btn.active {
background: linear-gradient(135deg, #00c6ff, #0072ff);
color: white;
border-color: transparent;
box-shadow: 0 4px 10px rgba(0, 198, 255, 0.3);
}
.sheet-btn:hover:not(.active) {
background: #e6f2ff;
}
/* --- EXCEL DİLİMLEYİCİ (SLICER) ALANI YENİ TASARIM --- */
.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; }
#slicerColInput {
min-width: 260px;
border: 1px solid #ced4da;
transition: all 0.3s ease;
}
#slicerColInput::placeholder {
color: #adb5bd;
}
#slicerColInput:focus::placeholder {
color: transparent;
}
#slicerColInput:focus {
border-color: #00c6ff;
box-shadow: 0 0 0 3px rgba(0, 198, 255, 0.15);
}
.slicer-area {
display: flex;
flex-direction: column;
gap: 15px;
}
.slicer-row {
display: flex;
align-items: center;
background: #fff;
padding: 15px;
border-radius: 8px;
border: 1px solid #e1e4e8;
box-shadow: 0 4px 10px rgba(0,0,0,0.04);
width: 100%;
}
.slicer-row-header {
flex: 0 0 250px; /* Genişletildi */
display: flex;
flex-direction: column;
border-right: 1px solid #e1e4e8;
margin-right: 15px;
padding-right: 15px;
}
.slicer-row-title { font-size: 14px; font-weight: 800; color: #1a1e23; margin-bottom: 5px; }
.slicer-row-stats {
font-size: 11px;
color: #6a737d;
font-weight: 600;
}
.slicer-remove-link {
color: #ff6b6b;
cursor: pointer;
font-weight: 800;
text-decoration: none;
transition: 0.2s;
margin-left: 2px;
}
.slicer-remove-link:hover {
color: #e63946;
text-decoration: underline;
}
.slicer-row-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
flex: 1;
}
.slicer-btn {
padding: 6px 14px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
border: 1px solid rgba(255,255,255,0.2);
cursor: pointer;
transition: all 0.2s ease;
/* İÇİNDE VERİ OLAN - CANLI CAM SİYAH */
background: rgba(0, 0, 0, 0.85);
color: #fff;
backdrop-filter: blur(5px);
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
}
/* SEÇİLİ OLAN (TIKLANAN) */
.slicer-btn.selected {
background: #000;
border: 1px solid #00c6ff;
box-shadow: 0 0 10px rgba(0, 198, 255, 0.6);
transform: scale(1.05);
}
.slicer-btn:hover:not(.disabled-slicer) {
transform: translateY(-2px);
background: #000;
}
/* PASİF OLAN - AÇIK GRİ */
.slicer-btn.disabled-slicer {
background: #e9ecef !important;
color: #adb5bd !important;
border: 1px solid #dee2e6 !important;
box-shadow: none !important;
cursor: not-allowed;
backdrop-filter: none;
transform: none !important;
}
/* --- TABLO 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 SÜTUN HARFLERİ (A, B, C...) */
.col-letter-header th {
text-align: center;
color: #adb5bd;
font-size: 16px;
font-weight: 800;
text-transform: uppercase;
background: #fff !important;
border: none !important;
border-bottom: 1px solid #e1e4e8 !important;
padding: 8px 12px;
pointer-events: 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; }
/* Eşleşen Metin Vurgusu (Açık Yeşil) */
.search-highlight {
background-color: #a5d6a7 !important;
color: #000 !important;
border-radius: 3px;
padding: 1px 2px;
font-weight: bold;
}
/* --- 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; }
.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; }
.search-highlight {
background-color: transparent !important;
color: inherit !important;
padding: 0 !important;
font-weight: normal !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>
<!-- 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">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 class="profile-photo-box">
<img id="profileImage" src="" alt="Kayıt Fotoğrafı" onerror="this.style.display='none'" onload="this.style.display='block'">
</div>
</div>
<div class="right">TEKNİK İŞLETME DAİRESİ BAŞKANLIĞI</div>
</div>
<!-- DOSYA YÜKLEME, ARAMA VE YAZDIRMA -->
<div class="controls no-print">
<div class="controls-top">
<input type="file" id="excelFile" accept=".xls,.xlsx">
<progress id="progressBar" value="0" max="100"></progress>
<button class="btn-secondary" type="button" onclick="window.location.reload()" title="Sayfayı Yenile">Sayfa Yenile</button>
<!-- ARAMA TEMİZLE BUTONU -->
<button class="btn-danger" type="button" onclick="clearGlobalSearch()" title="Aramayı Temizle">Ara Temizle</button>
<!-- GENEL ARAMA ALANI -->
<input type="text" id="globalSearch" placeholder="Tüm başlıklarda, tek yerde Ara..." oninput="applyFiltersAndSlicers()">
<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>
<!-- ÇOKLU SEKME (SHEET) LİSTESİ -->
<div id="sheetListContainer"></div>
</div>
<!-- EXCEL DİLİMLEYİCİ (SLICER) ALANI -->
<div class="slicer-container no-print">
<div class="slicer-header-bar">
<span>🎛️ Veri Dilimleyici Ekle:</span>
<!-- PENCERE VE BUTON OLUŞTUR ALANI -->
<input type="text" id="slicerColInput" placeholder="Sütun bilgisi gir (Örn: B, E, F, G)">
<button class="btn-primary" id="btnCreateSlicer">+ Buton Oluştur</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 currentWorkbook = null;
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;
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;
const sheetListContainer = document.getElementById('sheetListContainer');
if (sheetNames.length > 1) {
sheetListContainer.style.display = 'flex';
sheetListContainer.innerHTML = '';
sheetNames.forEach(sheet => {
const btn = document.createElement('button');
btn.className = 'sheet-btn';
btn.innerText = sheet;
btn.onclick = () => loadSheet(sheet);
sheetListContainer.appendChild(btn);
});
} else {
sheetListContainer.style.display = 'none';
sheetListContainer.innerHTML = '';
}
pb.value = 80;
loadSheet(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 loadSheet(sheetName) {
if (!currentWorkbook) return;
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() !== "");
});
} else {
alert(`"${sheetName}" sekmesinde veri bulunamadı!`);
excelData = [];
actualHeaders = {};
headerKeys = [];
}
currentSortCol = null;
sortAsc = true;
currentPage = 1;
document.getElementById('globalSearch').value = '';
document.getElementById('slicerArea').innerHTML = '';
document.getElementById('slicerColInput').value = '';
document.querySelectorAll('.sheet-btn').forEach(btn => {
if (btn.innerText === sheetName) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
renderHeaders();
currentFilteredData = excelData;
renderPagination();
updateGauges(excelData.length, excelData.length);
updateProfileImage(currentFilteredData);
}
function updateProfileImage(filteredData) {
const imgEl = document.getElementById('profileImage');
if (filteredData && filteredData.length > 0) {
let firstVal = filteredData[0]["D"];
if (firstVal !== undefined && String(firstVal).trim() !== "") {
let cleanVal = String(firstVal).trim();
imgEl.style.display = 'none';
imgEl.src = `Resim/${cleanVal}.jpg`;
} else {
imgEl.src = "";
imgEl.style.display = 'none';
}
} else {
imgEl.src = "";
imgEl.style.display = 'none';
}
}
function renderHeaders() {
const thead = document.getElementById('tableHead');
thead.innerHTML = '';
let letterRow = document.createElement('tr');
letterRow.className = 'col-letter-header no-print';
headerKeys.forEach(k => {
let th = document.createElement('th');
th.innerText = k;
letterRow.appendChild(th);
});
thead.appendChild(letterRow);
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 toSafeLower(str) {
return String(str)
.replace(/İ/g, 'i').replace(/I/g, 'ı')
.replace(/Ğ/g, 'ğ').replace(/Ü/g, 'ü')
.replace(/Ş/g, 'ş').replace(/Ö/g, 'ö')
.replace(/Ç/g, 'ç')
.toLowerCase();
}
function highlightMatch(text, searchVal) {
if (!searchVal) return text;
let lowerText = toSafeLower(text);
let lowerSearch = toSafeLower(searchVal);
let startIndex = 0;
let result = '';
let matchIndex = lowerText.indexOf(lowerSearch, startIndex);
if (matchIndex === -1) return text;
while (matchIndex !== -1) {
result += text.substring(startIndex, matchIndex);
result += `<span class="search-highlight">${text.substring(matchIndex, matchIndex + searchVal.length)}</span>`;
startIndex = matchIndex + searchVal.length;
matchIndex = lowerText.indexOf(lowerSearch, startIndex);
}
result += text.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 globalSearchVal = document.getElementById('globalSearch').value;
pageData.forEach(row => {
let tr = document.createElement('tr');
headerKeys.forEach(k => {
let td = document.createElement('td');
let cellText = row[k] !== undefined && row[k] !== null ? String(row[k]) : '';
td.innerHTML = highlightMatch(cellText, globalSearchVal);
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();
XLSX.utils.book_append_sheet(wb, ws, "Filtrelenmis_Veri");
XLSX.writeFile(wb, "Envanter_Raporu.xlsx");
}
// --- YENİ DİLİMLEYİCİ (BUTON OLUŞTUR) MANTIĞI ---
document.getElementById('btnCreateSlicer').addEventListener('click', function() {
const inputVal = document.getElementById('slicerColInput').value;
if (!inputVal) return;
const colsToCreate = inputVal.split(',').map(s => s.trim().toUpperCase()).filter(s => s);
colsToCreate.forEach(colKey => {
if (!headerKeys.includes(colKey)) return;
if (document.getElementById('slicer_' + colKey)) 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 rowBox = document.createElement('div');
rowBox.className = 'slicer-row slicer-box';
rowBox.id = 'slicer_' + colKey;
rowBox.dataset.col = colKey;
let buttonsHtml = sortedVals.map(v => `
<button class="slicer-btn" data-val="${v.replace(/"/g, '"')}">${v === "" ? "<i>(Boş Kayıtlar)</i>" : v}</button>
`).join('');
rowBox.innerHTML = `
<div class="slicer-row-header">
<div class="slicer-row-title">${headerName}</div>
<div class="slicer-row-stats" id="stats_${colKey}">Toplam: ${sortedVals.length} | Seçilen: 0 | Aktif: ${sortedVals.length} | <a class="slicer-remove-link" onclick="removeSlicer('${colKey}')" title="Bu filtreyi kaldır">Kaldır</a></div>
</div>
<div class="slicer-row-buttons">
${buttonsHtml}
</div>
`;
slicerArea.appendChild(rowBox);
rowBox.querySelectorAll('.slicer-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (this.classList.contains('disabled-slicer') && !this.classList.contains('selected')) return;
this.classList.toggle('selected');
applyFiltersAndSlicers();
});
});
});
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 = '';
document.getElementById('slicerColInput').value = '';
applyFiltersAndSlicers();
});
window.clearGlobalSearch = function() {
document.getElementById('globalSearch').value = '';
applyFiltersAndSlicers();
};
// --- DİLİMLEYİCİ ARAYÜZ (BUTON) GÜNCELLEMESİ ---
function updateSlicerUIs(slicerConds, globalSearchValText) {
const boxes = document.querySelectorAll('.slicer-box');
let globalSearchLower = toSafeLower(globalSearchValText);
boxes.forEach(box => {
const colKey = box.dataset.col;
let otherSlicerConds = slicerConds.filter(cond => cond.col !== colKey);
let subset = excelData;
if (otherSlicerConds.length > 0 || globalSearchLower) {
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 (globalSearchLower) {
passesSearch = headerKeys.some(k => {
let cellVal = row[k] !== undefined && row[k] !== null ? String(row[k]) : "";
return toSafeLower(cellVal).includes(globalSearchLower);
});
}
return passesSearch;
});
}
let validVals = new Set();
subset.forEach(row => {
let val = row[colKey] !== undefined ? String(row[colKey]).trim() : "";
validVals.add(val);
});
const buttons = Array.from(box.querySelectorAll('.slicer-btn'));
let activeCount = 0;
let selectedCount = 0;
buttons.forEach(btn => {
const val = btn.dataset.val;
const isSelected = btn.classList.contains('selected');
if (isSelected) selectedCount++;
if (validVals.has(val)) {
btn.classList.remove('disabled-slicer');
activeCount++;
} else {
btn.classList.add('disabled-slicer');
}
});
const statsSpan = document.getElementById('stats_' + colKey);
if (statsSpan) {
const totalCount = buttons.length;
statsSpan.innerHTML = `Toplam: ${totalCount} | Seçilen: ${selectedCount} | Aktif: ${activeCount} | <a class="slicer-remove-link" onclick="removeSlicer('${colKey}')" title="Bu filtreyi kaldır">Kaldır</a>`;
}
});
}
window.applyFiltersAndSlicers = function() {
const boxes = document.querySelectorAll('.slicer-box');
let slicerConds = [];
boxes.forEach(box => {
const colKey = box.dataset.col;
const checkedBtns = box.querySelectorAll('.slicer-btn.selected');
if (checkedBtns.length > 0) {
let allowedVals = Array.from(checkedBtns).map(btn => btn.dataset.val);
slicerConds.push({ col: colKey, allowed: allowedVals });
}
});
const globalSearchValRaw = document.getElementById('globalSearch').value;
const globalSearchLower = toSafeLower(globalSearchValRaw);
updateSlicerUIs(slicerConds, globalSearchValRaw);
let dataToProcess = excelData;
if (slicerConds.length > 0 || globalSearchLower) {
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 (globalSearchLower) {
passesSearch = headerKeys.some(k => {
let cellVal = row[k] !== undefined && row[k] !== null ? String(row[k]) : "";
return toSafeLower(cellVal).includes(globalSearchLower);
});
}
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);
updateProfileImage(currentFilteredData);
};
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>
