wolvox proje

Genel Forum
Cevapla
muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

wolvox proje

Mesaj gönderen muratca61 »

sorgu

Kod:Tümünü seç

wolvox isimli veritabanımıza bir tablo oluşturalım bu tablo 10 sütunlu olacak
sıra numarası (benzersiz) 
Telefon Numarası(numara harf karakter)
Başlama(15.09.2027 08:00 formatlı tarih)
Hizmet No(numara harf karakter)	
Operatör(numara harf karakter)	
Ad Soyad(numara harf karakter)	
Tc Kimlik (numara harf karakter)	
Açıklama / İçerik / Notlar(numara harf karakter)	
Doğum Tarihi(numara harf karakter)	
Tipi (numara harf karakter)	içerebilir olacak
muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

Re: wolvox proje

Mesaj gönderen muratca61 »

muratca61 yazdı: Çrş Kas 12, 2025 1:35 pm
sorgu

Kod:Tümünü seç

wolvox isimli veritabanımıza bir tablo oluşturalım bu tablo 10 sütunlu olacak
sıra numarası (benzersiz) 
Telefon Numarası(numara harf karakter)
Başlama(15.09.2027 08:00 formatlı tarih)
Hizmet No(numara harf karakter)	
Operatör(numara harf karakter)	
Ad Soyad(numara harf karakter)	
Tc Kimlik (numara harf karakter)	
Açıklama / İçerik / Notlar(numara harf karakter)	
Doğum Tarihi(numara harf karakter)	
Tipi (numara harf karakter)	içerebilir olacak

Kod:Tümünü seç

CREATE TABLE wolvox.musteri_kayitlari (
    id INT AUTO_INCREMENT PRIMARY KEY,                 -- sıra numarası (benzersiz)
    telefon_numarasi VARCHAR(80),                      -- Telefon Numarası
    baslama DATETIME,                                  -- Başlama (tarih-saat)
    hizmet_no VARCHAR(100),                             -- Hizmet No
    operator VARCHAR(80),                              -- Operatör
    ad_soyad VARCHAR(150),                             -- Ad Soyad
    tc_kimlik VARCHAR(50),                             -- Tc Kimlik
    aciklama TEXT,                                     -- Açıklama / İçerik / Notlar
    dogum_tarihi VARCHAR(50),                          -- Doğum Tarihi
    tipi VARCHAR(50)                                   -- Tipi
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

Re: wolvox proje

Mesaj gönderen muratca61 »

Kod:Tümünü seç

excel upload php sitesi kuralım veri çok fazla olacak. veri 30000 satırdan fazla bunu ve gecikmeyi göze al.

A sütunu;telefon_numarasi
B sütunu;baslama
C sütunu;hizmet_no
D sütunu;operator
E sütunu;ad_soyad
F sütunu;tc_kimlik
G sütunu;aciklama
H sütunu;dogum_tarihi
I sütunu;tipi
olacak şekilde.
burada dikkat edilecek husus bu bilgilerin birebir aynı olduğu excel satırları aktarılmayacak
 
muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

Re: wolvox proje

Mesaj gönderen muratca61 »

row rush sql eklentisi aynı satırları engelliyor

Kod:Tümünü seç

CREATE DATABASE IF NOT EXISTS wolvox CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE wolvox;

CREATE TABLE IF NOT EXISTS musteri_kayitlari (
    id INT AUTO_INCREMENT PRIMARY KEY,                 -- sıra numarası (benzersiz)
    telefon_numarasi VARCHAR(150),
    baslama DATETIME NULL,
    hizmet_no VARCHAR(200),
    operator VARCHAR(200),
    ad_soyad VARCHAR(300),
    tc_kimlik VARCHAR(150),
    aciklama TEXT,
    dogum_tarihi VARCHAR(150),
    tipi VARCHAR(150),
    row_hash CHAR(32) NOT NULL,                        -- tüm önemli alanlardan üretilen md5 hash
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY ux_row_hash (row_hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

Re: wolvox proje

Mesaj gönderen muratca61 »

Kod:Tümünü seç

index.php

Kod:Tümünü seç

<?php
// index.php
ini_set('display_errors', 1);
error_reporting(E_ALL);

// --- AYARLAR: veritabanı bilgilerinizi buraya yazın ---
$dbHost = '127.0.0.1';
$dbName = 'wolvox';
$dbUser = 'dbuser';
$dbPass = 'dbpass';
$uploadDir = __DIR__ . '/uploads';
$chunkSize = 1000; // her yüklemede kaç satır okunup işlenecek (gerektiğinde 500-2000 arası dene)

// composer autoload
require __DIR__ . '/vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;

// Basit chunk read filter
class ChunkReadFilter implements IReadFilter {
    private $startRow = 0;
    private $endRow = 0;
    public function setRows($startRow, $chunkSize) {
        $this->startRow = $startRow;
        $this->endRow = $startRow + $chunkSize - 1;
    }
    public function readCell($column, $row, $worksheetName = '') {
        if ($row >= $this->startRow && $row <= $this->endRow) {
            return true;
        }
        return false;
    }
}

// helper: normalize string for hashing
function norm($s) {
    if ($s === null) return '';
    $s = trim((string)$s);
    $s = preg_replace('/\s+/', ' ', $s);
    return mb_strtolower($s, 'UTF-8');
}

// try parse date in format d.m.Y H:i or d.m.Y
function parseDateToMySQL($val) {
    if ($val === null || $val === '') return null;
    // if PhpSpreadsheet already returned a DateTime object
    if ($val instanceof \DateTime) {
        return $val->format('Y-m-d H:i:s');
    }
    $v = trim((string)$val);
    // common Excel numeric date: PhpSpreadsheet gives float -> treat earlier
    if (is_numeric($v)) {
        // convert Excel timestamp
        try {
            $d = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($v);
            return $d->format('Y-m-d H:i:s');
        } catch (\Exception $e) {
            // fallthrough
        }
    }
    // try d.m.Y H:i
    $dt = \DateTime::createFromFormat('d.m.Y H:i', $v);
    if ($dt) return $dt->format('Y-m-d H:i:s');
    // try d.m.Y
    $dt = \DateTime::createFromFormat('d.m.Y', $v);
    if ($dt) return $dt->format('Y-m-d H:i:s');
    // try Y-m-d H:i:s
    $dt = \DateTime::createFromFormat('Y-m-d H:i:s', $v);
    if ($dt) return $dt->format('Y-m-d H:i:s');
    // as last resort, return null or original string stored as NULL (we store null)
    return null;
}

function respond($html) {
    echo $html;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['excel'])) {
    if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);

    $file = $_FILES['excel'];
    if ($file['error'] !== UPLOAD_ERR_OK) {
        respond("<p>Dosya yüklenirken hata: " . $file['error'] . "</p>");
        exit;
    }

    $filename = basename($file['name']);
    $targetPath = $uploadDir . '/' . time() . '_' . preg_replace('/[^a-zA-Z0-9._-]/','_', $filename);
    if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
        respond("<p>Dosya taşınamadı.</p>");
        exit;
    }

    // DB bağlantısı (PDO)
    $dsn = "mysql:host={$dbHost};dbname={$dbName};charset=utf8mb4";
    $pdo = new PDO($dsn, $dbUser, $dbPass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, // büyük veri için
    ]);

    $reader = new Xlsx();
    $reader->setReadDataOnly(true);

    // determine highest row count requires reading, but we'll loop chunks until no rows returned
    $filter = new ChunkReadFilter();
    $reader->setReadFilter($filter);

    $startRow = 2; // varsayılan: 1. satır başlık olduğu için 2'den başla
    $totalInserted = 0;
    $totalSkipped = 0;
    $batchSize = 500; // insert batch boyutu (daha büyük de olabilir)
    $insertBuffer = [];

    // prepared insert statement template (we will build multi-row insert per batch)
    // but we will use transaction per batch
    while (true) {
        $filter->setRows($startRow, $chunkSize);
        $spreadsheet = $reader->load($targetPath);
        $sheet = $spreadsheet->getActiveSheet();
        $rows = $sheet->toArray(null, true, true, true); // get as associative with columns A..I
        // free memory of spreadsheet after copying rows
        $spreadsheet->disconnectWorksheets();
        unset($spreadsheet);
        gc_collect_cycles();

        // remove possible header row in first chunk if present
        // if rows empty or only header, break
        if (count($rows) <= 1 && $startRow > 2) break;
        if (empty($rows)) break;

        // for each row in chunk
        foreach ($rows as $r => $cols) {
            // Note: $r is actual excel row number
            // map columns A..I:
            $telefon = isset($cols['A']) ? $cols['A'] : null;
            $baslamaRaw = isset($cols['B']) ? $cols['B'] : null;
            $hizmet_no = isset($cols['C']) ? $cols['C'] : null;
            $operator = isset($cols['D']) ? $cols['D'] : null;
            $ad_soyad = isset($cols['E']) ? $cols['E'] : null;
            $tc_kimlik = isset($cols['F']) ? $cols['F'] : null;
            $aciklama = isset($cols['G']) ? $cols['G'] : null;
            $dogum = isset($cols['H']) ? $cols['H'] : null;
            $tipi = isset($cols['I']) ? $cols['I'] : null;

            // normalize for hash
            $hashInput = implode('|', [
                norm($telefon),
                norm($baslamaRaw),
                norm($hizmet_no),
                norm($operator),
                norm($ad_soyad),
                norm($tc_kimlik),
                norm($aciklama),
                norm($dogum),
                norm($tipi)
            ]);
            $rowHash = md5($hashInput);

            // parse baslama to DATETIME
            $baslama = parseDateToMySQL($baslamaRaw);

            // prepare normalized values
            $insertBuffer[] = [
                'telefon' => (string)$telefon,
                'baslama' => $baslama,
                'hizmet_no' => (string)$hizmet_no,
                'operator' => (string)$operator,
                'ad_soyad' => (string)$ad_soyad,
                'tc_kimlik' => (string)$tc_kimlik,
                'aciklama' => (string)$aciklama,
                'dogum' => (string)$dogum,
                'tipi' => (string)$tipi,
                'hash' => $rowHash
            ];

            // when buffer reaches batchSize, flush to DB
            if (count($insertBuffer) >= $batchSize) {
                try {
                    $pdo->beginTransaction();
                    // build multi insert
                    $placeholders = [];
                    $values = [];
                    foreach ($insertBuffer as $row) {
                        $placeholders[] = "(?,?,?,?,?,?,?,?,?,?)";
                        $values[] = $row['telefon'];
                        $values[] = $row['baslama'];
                        $values[] = $row['hizmet_no'];
                        $values[] = $row['operator'];
                        $values[] = $row['ad_soyad'];
                        $values[] = $row['tc_kimlik'];
                        $values[] = $row['aciklama'];
                        $values[] = $row['dogum'];
                        $values[] = $row['tipi'];
                        $values[] = $row['hash'];
                    }
                    $sql = "INSERT IGNORE INTO musteri_kayitlari
                        (telefon_numarasi, baslama, hizmet_no, operator, ad_soyad, tc_kimlik, aciklama, dogum_tarihi, tipi, row_hash)
                        VALUES " . implode(',', $placeholders);
                    $stmt = $pdo->prepare($sql);
                    $stmt->execute($values);
                    $affected = $stmt->rowCount(); // inserted rows in this batch (PDO->rowCount with INSERT may vary)
                    $pdo->commit();
                    $totalInserted += $affected;
                    $insertBuffer = [];
                } catch (Exception $e) {
                    if ($pdo->inTransaction()) $pdo->rollBack();
                    respond("<p>DB hata: " . htmlspecialchars($e->getMessage()) . "</p>");
                    exit;
                }
            }
        }

        // move to next chunk
        $startRow += $chunkSize;

        // if fewer rows than chunkSize, we've reached EOF
        if (count($rows) < $chunkSize) break;
    }

    // flush remaining buffer
    if (count($insertBuffer) > 0) {
        try {
            $pdo->beginTransaction();
            $placeholders = [];
            $values = [];
            foreach ($insertBuffer as $row) {
                $placeholders[] = "(?,?,?,?,?,?,?,?,?,?)";
                $values[] = $row['telefon'];
                $values[] = $row['baslama'];
                $values[] = $row['hizmet_no'];
                $values[] = $row['operator'];
                $values[] = $row['ad_soyad'];
                $values[] = $row['tc_kimlik'];
                $values[] = $row['aciklama'];
                $values[] = $row['dogum'];
                $values[] = $row['tipi'];
                $values[] = $row['hash'];
            }
            $sql = "INSERT IGNORE INTO musteri_kayitlari
                (telefon_numarasi, baslama, hizmet_no, operator, ad_soyad, tc_kimlik, aciklama, dogum_tarihi, tipi, row_hash)
                VALUES " . implode(',', $placeholders);
            $stmt = $pdo->prepare($sql);
            $stmt->execute($values);
            $affected = $stmt->rowCount();
            $pdo->commit();
            $totalInserted += $affected;
            $insertBuffer = [];
        } catch (Exception $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            respond("<p>DB hata (flush): " . htmlspecialchars($e->getMessage()) . "</p>");
            exit;
        }
    }

    respond("<h2>İçe aktarma tamamlandı</h2>
    <p>Toplam eklenen satır (yaklaşık): " . intval($totalInserted) . "</p>
    <p>Yüklenen dosya: " . htmlspecialchars($filename) . "</p>
    <p>Not: 'Aynı tüm alanları birebir eşleşen' satırlar tekrar eklenmedi (unique row_hash ile).</p>");
    exit;
}

// Form göster
?>
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Excel Yükle & İçeri Aktar</title>
</head>
<body>
  <h1>Excel (.xlsx) Yükle</h1>
  <p>Excel sütun eşleşmesi (zorunlu):<br>
    A: telefon_numarasi, B: baslama (örn. 15.09.2027 08:00), C: hizmet_no, D: operator, E: ad_soyad, F: tc_kimlik, G: aciklama, H: dogum_tarihi, I: tipi
  </p>
  <form method="post" enctype="multipart/form-data">
    <input type="file" name="excel" accept=".xlsx,.xls,.csv" required>
    <br><br>
    <button type="submit">Yükle ve Aktar</button>
  </form>
</body>
</html>
muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

Re: wolvox proje

Mesaj gönderen muratca61 »

web adres yükleme için

Kod:Tümünü seç

https://mrt.muratca61.keenetic.link/araclar/wolvox/
web adres arama için

Kod:Tümünü seç

https://mrt.muratca61.keenetic.link/araclar/wolvox/public
muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

Re: wolvox proje

Mesaj gönderen muratca61 »

dosya yapısı

Kod:Tümünü seç

wolvox/
└─ public/ 
   └─ index.html
   └─ App.jsx
   └─ output.css
└─ api/
   └─ search.php
   └─ record.php


muratca61
Site Admin
Mesajlar: 35899
Kayıt: Cmt Ara 21, 2024 7:56 am

Re: wolvox proje

Mesaj gönderen muratca61 »

wolvo renk kodu

Kod:Tümünü seç

// ... (Önceki kodlar, COLUM_KEYS tanımına kadar aynı kalacak)

// Operatör değerlerine karşılık gelen renkleri (Tailwind CSS sınıfları) tanımlayalım
const OPERATOR_COLORS = {
    'İptal': 'bg-yellow-200 hover:bg-yellow-300', // Sarı
    'Cihaz': 'bg-green-200 hover:bg-green-300',   // Yeşil
    'Telekom İnternet': 'bg-purple-200 hover:bg-purple-300', // Mor
    'Türk Telekom': 'bg-blue-200 hover:bg-blue-300',     // Mavi
    'Vodafone': 'bg-red-200 hover:bg-red-300',     // Kırmızı
    'HATIRLATMA/NOT': 'bg-amber-300 hover:bg-amber-400', // Kahverengi (Amber tonu)
    'Sabit Hat': 'bg-cyan-200 hover:bg-cyan-300',     // Turkuaz (Cyan tonu)
    'Sim Kart Yenileme': 'bg-pink-200 hover:bg-pink-300', // Pembe
    'TAAHHÜT': 'bg-orange-200 hover:bg-orange-300',    // Turuncu
    'TİVİBU': 'bg-yellow-400 hover:bg-yellow-500',   // Hardal (Daha koyu Sarı/Yellow tonu)
    'default': 'hover:bg-gray-100' // Tanımlanmayanlar için varsayılan (mevcut hover stili)
};

// --- Kopyalama Fonksiyonu ---
// ... (Bu fonksiyon aynı kalacak)
async function copyToClipboard(text) {
    if (!text) return false;
    try {
        await navigator.clipboard.writeText(text);
        return true;
    } catch (err) {
        console.error('Kopyalama hatası:', err);
        return false;
    }
}

// --- Hücre Kopyalama Butonunun HTML'i ---
// ... (Bu fonksiyon aynı kalacak)
function getCopyButtonHtml(value) {
    const cleanedValue = value.replace(/<[^>]*>/g, '').trim();
    if (!cleanedValue) return '';

    return `
        <button class="copy-icon p-0.5 rounded hover:bg-gray-300 text-gray-500 hover:text-blue-600"
            data-copy-value="${cleanedValue.replace(/"/g, '&quot;')}"
            title="Bu değeri kopyala">
            <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2v-2M8 5a2 2 0 002 2h4a2 2 0 002-2M8 5a2 2 0 00-2 2v2M16 3h2a2 2 0 012 2v2" />
            </svg>
        </button>
    `;
}

// --- VURGULAMA FONKSİYONU (Aynı Kaldı) ---
// ... (Bu fonksiyon aynı kalacak)
function highlightText(text, query) {
    if (!text || !query) return text ?? '';

    let cleanedText = String(text).replace(/_x000D_/g, '');
    const termGroups = query.split(';');
    let searchTerms = [];
    termGroups.forEach(group => {
        group.split(',').forEach(term => {
            const trimmedTerm = term.trim();
            if (trimmedTerm) {
                searchTerms.push(trimmedTerm.toLocaleLowerCase('tr'));
            }
        });
    });

    searchTerms = [...new Set(searchTerms)].filter(t => t.length > 0);
    
    if (searchTerms.length === 0) return cleanedText;

    let highlightedHtml = cleanedText;

    searchTerms.forEach(term => {
        const termLength = term.length;
        let outputParts = [];
        
        highlightedHtml.split(/(<span[^>]*>[^<]*<\/span>)/g).forEach(part => {
            if (part.startsWith('<span')) {
                outputParts.push(part);
            } else {
                let currentSegment = part;
                let currentStartIndex = 0;
                const segmentLower = currentSegment.toLocaleLowerCase('tr');
                let matchIndex;

                while ((matchIndex = segmentLower.indexOf(term, currentStartIndex)) !== -1) {
                    outputParts.push(currentSegment.substring(currentStartIndex, matchIndex));
                    const matchedText = currentSegment.substring(matchIndex, matchIndex + termLength);
                    outputParts.push(`<span class="highlight">${matchedText}</span>`);
                    currentStartIndex = matchIndex + termLength;
                }
                outputParts.push(currentSegment.substring(currentStartIndex));
            }
        });

        if (outputParts.length > 1) {
             highlightedHtml = outputParts.join('');
        }
    });

    return highlightedHtml;
}
// ------------------------------------------------

// --- SIRALAMA İŞLEVİ (Aynı Kaldı) ---
// ... (Bu fonksiyon aynı kalacak)
function sortData(data, key, direction) {
    if (!key || key === 'index') return data;
    
    return data.sort((a, b) => {
        let valA = a[key];
        let valB = b[key];
        
        if (key === 'baslama') {
            valA = new Date(valA);
            valB = new Date(valB);
        } else if (typeof valA === 'string' && typeof valB === 'string') {
            return valA.localeCompare(valB, 'tr', { sensitivity: 'base' }) * (direction === 'asc' ? 1 : -1);
        }

        if (valA < valB) return direction === 'asc' ? -1 : 1;
        if (valA > valB) return direction === 'asc' ? 1 : -1;
        return 0;
    });
}

// --- TABLO OLUŞTURMA İŞLEVİ (Güncellendi) ---
function renderTable(data, query, sortConfig) {
    if(!Array.isArray(data) || data.length === 0) {
        resultsDiv.innerHTML = '<p class="text-center text-gray-500 mt-8 text-lg p-4 bg-white rounded-xl shadow-md">Sonuç bulunamadı.</p>';
        resultsCountDiv.innerHTML = '';
        return;
    }

    const sortedData = sortData(data, sortConfig.key, sortConfig.direction);
    resultsCountDiv.innerHTML = `<span class="text-blue-600 font-bold">${data.length}</span> Kayıt bulundu.`;

    let html = `<table class="min-w-full bg-white divide-y divide-gray-200 fixed-table">
        <thead class="bg-blue-50 sticky top-0"> 
            <tr>`;
                
    // Dinamik Başlıklar ve Genişlik Uygulaması
    COLUMN_KEYS.forEach(col => {
        const isCurrentSort = sortConfig.key === col.key;
        const sortArrow = isCurrentSort && col.key !== 'index' ? (sortConfig.direction === 'desc' ? ' ⬇️' : ' ⬆️') : '';
        const color = isCurrentSort && col.key !== 'index' ? 'text-blue-700' : 'text-gray-600';
        const isSortable = col.key !== 'index';
        
        html += `<th id="${col.key}Header" 
            class="p-3 text-left text-sm font-semibold uppercase ${color} ${isSortable ? 'sortable-header' : ''}"
            style="width: ${col.width};">
            ${col.label}${sortArrow}
        </th>`;
    });

    html += `</tr></thead>
        <tbody class="divide-y divide-gray-100">`;

    sortedData.forEach((row, index) => {
        // ❗ BURASI GÜNCELLENDİ: Operatör değerine göre satır rengi belirleniyor
        const operatorValue = row['operator'] ? row['operator'].trim() : '';
        const rowColorClass = OPERATOR_COLORS[operatorValue] || OPERATOR_COLORS['default'];

        html += `<tr class="${rowColorClass} transition-colors duration-150">`;
            
        // # Sütunu
        html += `<td class="p-3 text-sm font-medium text-gray-900 relative">
            <span class="content-wrapper">${index+1}</span>
            ${getCopyButtonHtml(String(index+1))}
        </td>`;

        // Veri Sütunları
        COLUMN_KEYS.filter(col => col.key !== 'index').forEach(col => {
            const originalValue = row[col.key] || '';
            let content = highlightText(originalValue, query);
            
            // Kopyalama ikonu her hücreye eklendi
            html += `<td class="p-3 text-sm text-gray-700 relative">
                <span class="content-wrapper">${content}</span>
                ${getCopyButtonHtml(originalValue)}
            </td>`;
        });

        html += `</tr>`;
    });

    html += '</tbody></table>';
    resultsDiv.innerHTML = html;
    
    // ⬇️ Tıklama olaylarını atama
    COLUMN_KEYS.filter(col => col.key !== 'index').forEach(col => {
        document.getElementById(`${col.key}Header`).onclick = function() {
            if (currentSort.key === col.key) {
                currentSort.direction = currentSort.direction === 'desc' ? 'asc' : 'desc';
            } else {
                currentSort.key = col.key;
                currentSort.direction = (col.key === 'baslama' || col.key === 'index') ? 'desc' : 'asc';
            }
            renderTable(data, query, currentSort); 
        };
    });
    
    // ⬇️ Kopyalama butonu olaylarını atama
    document.querySelectorAll('.copy-icon').forEach(button => {
        button.addEventListener('click', async function(event) {
            event.stopPropagation();
            const value = this.getAttribute('data-copy-value');
            const originalIcon = this.innerHTML;
            
            const success = await copyToClipboard(value);
            
            if (success) {
                this.innerHTML = `<svg class="w-3 h-3 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>`;
            } else {
                 this.innerHTML = `<svg class="w-3 h-3 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>`;
            }
            
            setTimeout(() => {
                this.innerHTML = originalIcon;
            }, 1000);
        });
    });
}


// --- ANA ARAMA İŞLEVİ ---
// ... (Bu fonksiyon aynı kalacak)
async function handleSearch() {
    clearTimeout(timeout);
    const query = searchInput.value.trim();
    
    if(query === '') {
        resultsDiv.innerHTML = '';
        resultsCountDiv.innerHTML = '';
        return;
    }

    resultsDiv.innerHTML = '<p class="text-blue-600 text-center mt-5 text-lg p-4">Yükleniyor...</p>';
    resultsCountDiv.innerHTML = '';

    timeout = setTimeout(async () => {
        try {
            const res = await fetch(`${API_URL}?q=${encodeURIComponent(query)}`);
            const text = await res.text();

            let data;
            try {
                data = JSON.parse(text);
            } catch(e) {
                resultsDiv.innerHTML = '<p class="text-center text-red-500 p-4">Sunucudan hatalı veri geldi.</p>';
                return;
            }
            
            currentData = data; 
            renderTable(currentData, query, currentSort);

        } catch(err) {
            resultsDiv.innerHTML = '<p class="text-center text-red-500 p-4">Veri çekilirken bir hata oluştu.</p>';
        }
    }, 300);
}

// --- İLK ÇALIŞTIRMA VE OLAY İŞLEYİCİLER ---
// ... (Bu kısım aynı kalacak)
searchInput.addEventListener('input', handleSearch);

clearSearchButton.addEventListener('click', function() {
    searchInput.value = '';
    clearSearchButton.style.display = 'none';
    resultsDiv.innerHTML = '';
    resultsCountDiv.innerHTML = '';
    clearTimeout(timeout);
    currentData = [];
    currentSort = { key: 'baslama', direction: 'desc' };
});

searchInput.addEventListener('input', function() {
    clearSearchButton.style.display = this.value.trim() ? 'flex' : 'none';
});
</script>
Cevapla