window-calculator-pro / index.html
escambalkon's picture
verilere dışardan erişmek için veri tabanı kursun
426ea2d verified
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pencere Ölçü Hesaplama - Profil Bazlı Cam Formülü</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script>
<script src="api-client.js"></script>
<link rel="stylesheet" href="style.css">
button { padding: 12px 20px; background: #4a90e2; color: white; border: none; border-radius: 6px; cursor: pointer; margin: 5px; }
button:hover { background: #3a7bc8; transform: translateY(-2px); }
.form-group { margin-bottom: 15px; }
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2d3748;
}
input, select, textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
margin-top: 5px;
}
.input-row { display: flex; gap: 15px; }
.input-row > * { flex: 1; }
.settings-section { margin-bottom: 25px; padding: 20px; background: #f8fafc; border-radius: 8px; }
.delete-btn { background: #e53e3e !important; }
.export-btn { background: #38a169 !important; }
.pdf-btn { background: #e53e3e !important; }
.catalog-btn { background: #805ad5 !important; }
.add-btn { background: #38a169 !important; }
.save-btn { background: #2b6cb0 !important; }
.edit-btn { background: #d69e2e !important; }
.empty-state { text-align: center; padding: 20px; color: #718096; font-style: italic; }
.customer-pos-list { margin-top: 15px; max-height: 400px; overflow-y: auto; }
.customer-pos-item { background: white; margin: 8px 0; padding: 12px; border: 1px solid #e2e8f0; border-radius: 6px; }
.customer-pos-info { margin-bottom: 8px; }
.customer-pos-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.customer-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.image-preview { max-width: 100px; max-height: 100px; border: 1px solid #ddd; border-radius: 6px; margin: 5px 0; }
.system-image { max-width: 150px; max-height: 150px; border: 2px solid #4a90e2; border-radius: 8px; margin: 10px 0; }
.part-image { max-width: 80px; max-height: 80px; border: 1px solid #ddd; border-radius: 4px; margin-right: 10px; }
.result-part { display: flex; align-items: center; margin: 10px 0; padding: 10px; background: #f8fafc; border-radius: 6px; }
.system-with-image { display: flex; align-items: center; gap: 15px; margin: 10px 0; }
.customer-management { display: flex; gap: 15px; margin-bottom: 20px; }
.customer-form { flex: 1; padding: 15px; background: #f8fafc; border-radius: 8px; }
.customer-list { flex: 1; padding: 15px; background: #f8fafc; border-radius: 8px; }
.customer-pos-section { flex: 2; padding: 15px; background: #f8fafc; border-radius: 8px; }
.customer-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #e2e8f0;
cursor: pointer;
/* Dikdörtgen düzeni */
margin: 5px 0;
border-radius: 8px;
background: white;
border: 1px solid #e2e8f0;
transition: all 0.2s ease;
}
.customer-item:hover { background: #edf2f7; }
.customer-item.selected { background: #e0f2fe; border-color: #3b82f6; }
.customer-pos-section .customer-header { margin-bottom: 15px; }
.customer-pos-section h3 { color: #2d3748; }
#selectedCustomerInfo { font-size: 14px; color: #4a5568; font-weight: 500; }
#selectedCustomerInfo span { background: #e0f2fe; color: #0369a1; padding: 4px 8px; border-radius: 4px; }
/* Modal Stilleri */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 30px;
border-radius: 12px;
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
z-index: 1001;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 15px;
}
.modal-header h2 { margin: 0; color: #2b6cb0; }
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #718096;
z-index: 1002;
}
.close-btn:hover { color: #e53e3e; }
/* Sistem Listesi Stilleri - Dikdörtgen düzeni */
.systems-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-top: 20px;
}
.system-item {
background: white;
padding: 20px;
border-radius: 12px;
border: 2px solid #e2e8f0;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
/* Dikdörtgen boyutlandırma */
min-height: 160px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.system-item:hover {
border-color: #4a90e2;
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
}
.system-item.active {
border-color: #4a90e2;
background: #f0f7ff;
}
.system-item-image {
max-width: 60px;
max-height: 40px;
border-radius: 6px;
margin: 0 auto 15px;
display: block;
/* Dikdörtgen aspect ratio */
width: 100%;
height: auto;
object-fit: contain;
}
.system-item-name {
font-size: 18px;
font-weight: 600;
color: #2d3748;
margin-bottom: 10px;
line-height: 1.3;
}
.system-item-parts {
font-size: 14px;
color: #718096;
margin-bottom: 15px;
font-weight: 500;
}
.system-item-actions {
display: flex;
gap: 8px;
justify-content: center;
flex-wrap: wrap;
margin-top: auto;
}
.system-item-actions button {
padding: 8px 12px;
font-size: 12px;
margin: 2px;
flex: 1;
min-width: 65px;
border-radius: 6px;
}
/* Profil Listesi Stilleri */
.parts-list {
margin-top: 12px;
padding: 12px;
background: #f8fafc;
border-radius: 6px;
display: none;
border: 1px solid #e2e8f0;
}
.parts-list.show {
display: block;
}
.part-details {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid #e2e8f0;
}
.part-details:last-child {
border-bottom: none;
}
.part-info {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.part-actions {
display: flex;
gap: 4px;
}
.part-actions button {
padding: 4px 8px;
font-size: 10px;
margin: 0;
border-radius: 4px;
}
/* Cam Formül Bölümü */
.glass-formula-section {
margin: 15px 0;
padding: 15px;
background: #f0fff4;
border-radius: 8px;
border: 1px solid #c6f6d5;
}
.glass-formula-section h4 {
color: #2f855a;
margin-bottom: 15px;
}
.formula-input-group {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.formula-input-group input, .formula-input-group select {
flex: 1;
}
.formula-description {
font-size: 12px;
color: #718096;
margin-top: 5px;
padding: 8px;
background: #f7fafc;
border-radius: 4px;
border-left: 3px solid #4a90e2;
}
.formula-preview {
margin-top: 15px;
padding: 10px;
background: #edf2f7;
border-radius: 6px;
font-family: monospace;
display: none;
}
.formula-preview.show {
display: block;
}
.formula-test {
margin-top: 15px;
padding: 15px;
background: #fffaf0;
border-radius: 6px;
border: 1px solid #fbd38d;
display: none;
}
.formula-test.show {
display: block;
}
.formula-test-result {
margin-top: 10px;
padding: 10px;
background: #f0fff4;
border-radius: 4px;
font-weight: bold;
display: none;
}
.formula-test-result.show {
display: block;
}
.toggle-section-btn {
background: #d69e2e !important;
width: 100%;
margin-bottom: 10px;
}
.toggle-section-btn:hover {
background: #b7791f !important;
}
/* Profil Açıklama Alanı */
.part-description-input {
min-height: 80px;
resize: vertical;
}
/* Poz Listesi Stilleri */
.pos-list { margin-top: 20px; }
.pos-item {
background: white;
margin: 15px 0;
padding: 20px;
border: 1px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 3px 6px rgba(0,0,0,0.05);
/* Dikdörtgen düzeni */
display: flex;
flex-direction: column;
}
.pos-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 12px;
border-bottom: 2px solid #e2e8f0;
}
.pos-title {
font-size: 18px;
font-weight: 700;
color: #2d3748;
line-height: 1.3;
}
.pos-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 15px;
margin-bottom: 15px;
}
.pos-detail-item {
padding: 12px;
background: #f8fafc;
border-radius: 8px;
border-left: 3px solid #4a90e2;
}
.pos-detail-label {
font-size: 13px;
color: #718096;
margin-bottom: 6px;
font-weight: 600;
}
.pos-detail-value {
font-size: 15px;
font-weight: 700;
color: #2d3748;
line-height: 1.4;
}
.pos-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: auto;
padding-top: 15px;
}
/* Poz Düzenleme Modal Stilleri */
.edit-pos-form {
margin-top: 20px;
}
/* Kaydetme Bildirimi */
.save-notification {
position: fixed;
top: 20px;
right: 20px;
background: #38a169;
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
display: none;
animation: slideIn 0.3s ease-out;
}
/* Storage Uyarısı */
.storage-warning {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #e53e3e;
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
display: none;
animation: slideIn 0.3s ease-out;
max-width: 500px;
text-align: center;
}
/* Cari Düzenleme Modal Stilleri */
.customer-edit-form {
max-width: 100%;
}
.customer-edit-form .form-group {
margin-bottom: 15px;
}
.customer-edit-form input[readonly] {
background-color: #f8fafc !important;
color: #718096 !important;
cursor: not-allowed;
}
.customer-edit-form textarea {
min-height: 80px;
resize: vertical;
}
/* Cari Poz İndirme Butonu */
.download-all-btn {
background: #805ad5 !important;
margin-top: 10px;
width: 100%;
}
.download-all-btn:hover {
background: #6b46c1 !important;
}
/* Seçim Butonları */
.select-all-btn {
background: #d69e2e !important;
margin-right: 10px;
}
.deselect-all-btn {
background: #718096 !important;
margin-right: 10px;
}
.generate-selected-btn {
background: #38a169 !important;
}
/* Gelişmiş Formül Stilleri */
.advanced-formula-section {
margin: 15px 0;
padding: 15px;
background: #fffaf0;
border-radius: 8px;
border: 1px solid #fbd38d;
}
.formula-help {
font-size: 12px;
color: #718096;
margin-top: 5px;
padding: 8px;
background: #f7fafc;
border-radius: 4px;
border-left: 3px solid #d69e2e;
}
.formula-examples {
margin-top: 10px;
font-size: 11px;
color: #4a5568;
}
.formula-example {
padding: 4px;
margin: 2px 0;
background: #f7fafc;
border-radius: 3px;
font-family: monospace;
}
/* Poz Seçim Kontrolleri */
.pos-selection-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
align-items: center;
}
.pos-checkbox {
margin-right: 10px;
transform: scale(1.2);
}
.selected-count {
font-weight: bold;
color: #2b6cb0;
margin-left: auto;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@media (max-width: 768px) {
.input-row { flex-direction: column; }
.customer-pos-actions, .customer-header, .system-with-image { flex-direction: column; align-items: flex-start; }
.customer-management { flex-direction: column; }
.modal-content { width: 95%; padding: 20px; }
.systems-list { grid-template-columns: 1fr; }
.system-item-actions { flex-direction: column; }
.system-item-actions button {
width: 100%;
margin: 2px 0;
}
.part-details { flex-direction: column; align-items: flex-start; gap: 8px; }
.part-actions { align-self: flex-end; }
.formula-input-group { flex-direction: column; }
.pos-details { grid-template-columns: 1fr; }
.pos-selection-controls { flex-direction: column; align-items: flex-start; }
.selected-count { margin-left: 0; margin-top: 10px; }
.customer-edit-form .input-row { flex-direction: column; }
.customer-header { flex-direction: row; }
.company-info-display { flex-direction: column; align-items: center; text-align: center; }
.company-logo { width: 100px; height: 100px; }
.nav { flex-direction: column; gap: 15px; }
.nav-left { justify-content: center; }
.nav-logo { width: 158px; height: 105px; }
/* Sayfa bazlı ayarlar responsive */
#perPageSettingsContainer .input-row { flex-direction: column; }
#perPageSettingsContainer select, #perPageSettingsContainer button { width: 100%; }
.page-settings-item { flex-direction: column; align-items: flex-start; }
.page-settings-actions { align-self: flex-end; margin-top: 10px; }
}
/* Sayfa Bazlı PDF Ayarları Stilleri */
.page-settings-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background: #f8fafc;
border-radius: 6px;
border: 1px solid #e2e8f0;
margin: 5px 0;
transition: all 0.2s ease;
}
.page-settings-item:hover {
background: #edf2f7;
border-color: #4a90e2;
}
.page-settings-info {
flex: 1;
}
.page-settings-name {
font-weight: 600;
color: #2d3748;
margin-bottom: 4px;
}
.page-settings-description {
font-size: 12px;
color: #718096;
}
.page-settings-actions {
display: flex;
gap: 8px;
}
.page-settings-actions button {
padding: 4px 8px;
font-size: 10px;
margin: 0;
border-radius: 4px;
}
/* Sayfa türü göstergeleri */
.page-type-indicator {
display: inline-block;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
font-weight: bold;
text-transform: uppercase;
}
.page-type-title { background: #bee3f8; color: #2b6cb0; }
.page-type-header { background: #c6f6d5; color: #2f855a; }
.page-type-content { background: #fbb6ce; color: #b83280; }
.page-type-summary { background: #fef5e7; color: #d69e2e; }
</head>
<body>
<div class="container">
<nav class="main-nav">
<div class="nav-left">
<button onclick="showPage('mainPage')" class="nav-btn">Ana Sayfa</button>
<button onclick="showPage('settingsPage')" class="nav-btn">Ayarlar</button>
<button onclick="showPage('customerPage')" class="nav-btn">Cariler</button>
<button onclick="showPage('backupPage')" class="nav-btn">Yedekleme</button>
</div>
<!-- MongoDB Atlas Settings Modal -->
<div id="atlasSettingsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>☁️ MongoDB Atlas Ayarları</h2>
<button class="close-btn" onclick="closeAtlasSettingsModal()">&times;</button>
</div>
<div class="settings-section">
<div style="margin-bottom: 20px; padding: 15px; background: #f8fafc; border-radius: 8px; border-left: 4px solid #4a90e2;">
<h4 style="color: #2b6cb0; margin-bottom: 10px;">📋 MongoDB Atlas Hızlı Kurulum</h4>
<ol style="margin: 0; padding-left: 20px; font-size: 14px; line-height: 1.6;">
<li><a href="https://www.mongodb.com/atlas" target="_blank" style="color: #4a90e2;">MongoDB Atlas</a>'a kaydolun (ücretsiz)</li>
<li>Yeni cluster oluşturun (M0 Sandbox - ücretsiz)</li>
<li>Database User oluşturun (username/password)</li>
<li>Network Access'te IP adresinizi ekleyin (0.0.0.0/0 - tüm IP'ler)</li>
<li>Aşağıdaki bağlantı bilgilerini girin</li>
</ol>
</div>
<div class="form-group">
<label>MongoDB Connection String:</label>
<textarea id="atlasConnectionString"
placeholder="mongodb+srv://username:password@cluster.mongodb.net/pencere-hesaplama?retryWrites=true&w=majority"
style="min-height: 80px; font-family: monospace; font-size: 12px;"
onchange="validateAtlasConnection()"></textarea>
<small style="color: #718096;">
Connection String'i MongoDB Atlas Dashboard'dan kopyalayın
</small>
</div>
<div class="input-row">
<div class="form-group">
<label>Database Name:</label>
<input type="text" id="atlasDatabaseName" value="pencere-hesaplama" placeholder="Veritabanı adı">
</div>
<div class="form-group">
<label>Cluster Name:</label>
<input type="text" id="atlasClusterName" placeholder="Cluster adı">
</div>
</div>
<div id="atlasValidationResult" style="margin: 15px 0; padding: 15px; border-radius: 6px; display: none;">
<!-- Validation result will be shown here -->
</div>
<div class="button-group">
<button onclick="testAtlasConnection()" class="edit-btn">🔍 Bağlantı Test Et</button>
<button onclick="setupAtlasDatabase()" class="add-btn">🚀 Atlas'ı Kur</button>
<button onclick="generateAtlasConfig()" class="catalog-btn">⚙️ Config Oluştur</button>
</div>
<div style="margin-top: 20px; padding: 15px; background: #fffaf0; border-radius: 8px; border: 1px solid #fbd38d;">
<h4 style="color: #d69e2e; margin-bottom: 10px;">💡 İpucu</h4>
<p style="margin: 0; font-size: 14px; color: #718096;">
Varsayılan demo bağlantıyı kullanabilir veya kendi Atlas cluster'ınızı kurabilirsiniz.
Demo bağlantısı test amaçlıdır ve verileriniz herkese açık olabilir.
</p>
</div>
</div>
</div>
</div>
<div class="nav-center">
<span id="userInfo" class="user-info">Hoş geldiniz!</span>
</div>
<div class="nav-right">
<button onclick="logout()" class="nav-btn logout-btn">Çıkış</button>
<div class="nav-logo-container">
<img id="navCompanyLogo" src="" alt="Firma Logosu" class="nav-logo" style="display: none;">
</div>
</div>
</nav>
<!-- Ana Sayfa -->
<div id="mainPage" class="page-content">
<h1 class="page-title">Pencere Ölçü Hesaplama</h1>
<div class="calculation-section">
<div class="form-group">
<label for="systemSelect">Sistem Seçin:</label>
<select id="systemSelect" onchange="selectSystemForCalculation(this.value)">
<option value="">Sistem seçin...</option>
</select>
</div>
<div id="selectedSystemPreview" class="system-preview" style="display: none;">
<h4>Seçilen Sistem:</h4>
<div id="systemPreview"></div>
</div>
<div class="input-row">
<div class="form-group">
<label for="width">Yatay (mm):</label>
<input type="number" id="width" placeholder="Genişlik">
</div>
<div class="form-group">
<label for="height">Dikey (mm):</label>
<input type="number" id="height" placeholder="Yükseklik">
</div>
<div class="form-group">
<label for="quantity">Adet:</label>
<input type="number" id="quantity" value="1">
</div>
</div>
<div class="input-row">
<div class="form-group">
<label for="projectName">Proje:</label>
<input type="text" id="projectName" placeholder="Proje adı">
</div>
<div class="form-group">
<label for="customerName">Cari:</label>
<div class="customer-input-group">
<input type="text" id="customerName" placeholder="Cari adı" list="customerList">
<datalist id="customerList"></datalist>
<div id="newCustomerButtonContainer"></div>
</div>
</div>
</div>
<button onclick="calculateAndAddPos()" class="primary-btn">Hesapla ve Poz Ekle</button>
</div>
<!-- Poz Listesi Bölümü -->
<div class="pos-list-section" id="posListContainer">
<h3>Hesaplanan Pozlar</h3>
<div class="storage-info">
<small>Poz Sayısı: <span id="posCount">0</span> |
Tahmini Depolama: <span id="storageSize">0 KB</span></small>
</div>
<div id="posList"></div>
<div class="pos-actions">
<button onclick="generateAllPosPDF()" class="pdf-btn">Tüm Pozları PDF Oluştur</button>
<button onclick="saveAllPos()" class="save-btn">Hesaplanan Pozları Kaydet</button>
<button onclick="clearPosList()" class="delete-btn">Poz Listesini Temizle</button>
</div>
</div>
</div>
<!-- Ayarlar Sayfası -->
<div id="settingsPage" class="page-content" style="display: none;">
<h1 class="page-title">Ayarlar</h1>
<div class="settings-section">
<h2>🗄️ Veritabanı Kurulumu</h2>
<!-- MongoDB Atlas Connection Info -->
<div id="atlasConnectionInfo" style="margin-bottom: 20px; padding: 15px; background: #f0f7ff; border-radius: 8px; border: 1px solid #4a90e2;">
<h4 style="color: #2b6cb0; margin-bottom: 10px;">🌐 MongoDB Atlas Bulut Veritabanı</h4>
<p style="font-size: 14px; color: #4a5568; margin-bottom: 10px;">
Uygulama, MongoDB Atlas bulut veritabanını kullanmaktadır. Verileriniz güvenli bir şekilde saklanır ve her yerden erişilebilir.
</p>
<div id="connectionStatus" style="margin-top: 10px; padding: 10px; background: white; border-radius: 6px;">
<div style="display: flex; align-items: center; gap: 10px;">
<div class="loading-spinner" style="width: 16px; height: 16px;"></div>
<span>Bağlantı kontrol ediliyor...</span>
</div>
</div>
</div>
<div id="databaseStatus" class="database-status">
<div id="dbStatusContent">
<h4>Veritabanı Durumu Kontrol Ediliyor...</h4>
<div class="loading-container">
<div class="loading-spinner"></div>
<span>Veritabanı bağlantısı kuruluyor...</span>
</div>
</div>
</div>
<div class="button-group">
<button onclick="quickSetupDatabase()" class="add-btn">🚀 Veritabanını Kur</button>
<button onclick="checkDatabaseStatus()" class="edit-btn">📊 Durumu Kontrol Et</button>
<button onclick="openAtlasSettings()" class="catalog-btn">☁️ Atlas Ayarları</button>
<button onclick="resetDatabase()" class="delete-btn">🔄 Veritabanını Sıfırla</button>
</div>
</div>
<div class="settings-section">
<h2>🏢 Firma Bilgileri</h2>
<div class="button-group">
<button onclick="openCompanyInfoModal()" class="add-btn">🏢 Firma Bilgilerini Düzenle</button>
<button onclick="previewCompanyInfo()" class="catalog-btn">👁️ Önizleme</button>
</div>
</div>
<div class="settings-section">
<h2>🔧 Sistem Yönetimi</h2>
<div class="button-group">
<button onclick="openSystemModal()" class="add-btn">+ Yeni Sistem Ekle</button>
<button onclick="openSystemsModal()" class="catalog-btn">📋 Mevcut Sistemleri Görüntüle</button>
</div>
</div>
<div class="settings-section">
<h2>📄 PDF Ayarları</h2>
<div class="button-group">
<button onclick="openPDFSettingsModal()" class="add-btn">⚙️ PDF Ayarları</button>
<button onclick="previewPDFSettings()" class="preview-btn">👁️ PDF Önizleme</button>
<button onclick="resetPDFSettings()" class="reset-btn">🔄 Varsayılana Sıfırla</button>
</div>
</div>
<div class="settings-section">
<h2>💾 Veri Yönetimi</h2>
<div class="button-group">
<button onclick="exportAllData()" class="export-btn">Tüm Verileri Dışa Aktar</button>
<button onclick="importData()" class="add-btn">Veri İçe Aktar</button>
<button onclick="clearAllData()" class="delete-btn">Tüm Verileri Temizle</button>
</div>
<div class="storage-info">
<h4>Depolama Durumu</h4>
<div id="storageStatus"></div>
</div>
</div>
</div>
<!-- Cariler Sayfası -->
<div id="customerPage" class="page-content" style="display: none;">
<h1 class="page-title">Cariler</h1>
<div class="customer-management">
<div class="customer-list-section">
<div class="section-header">
<h3>Cari Listesi</h3>
<button onclick="openAddCustomerModal()" class="add-btn">+ Yeni Cari Ekle</button>
</div>
<div class="form-group">
<input type="text" id="customerSearch" placeholder="Cari ara..." onkeyup="filterCustomers()">
</div>
<div id="customerListContainer" class="customer-list-container">
<div class="empty-state">Henüz cari yok</div>
</div>
</div>
<div class="customer-pos-section">
<div class="section-header">
<h3>Seçili Cari Poz Listesi</h3>
<div id="selectedCustomerInfo" style="display: none;">
<span id="selectedCustomerName"></span>
</div>
</div>
<div id="customerPosContainer" class="customer-pos-container">
<div class="empty-state">Cari seçmek için listeden bir cariye tıklayın</div>
</div>
</div>
</div>
</div>
<!-- Yedekleme Sayfası -->
<div id="backupPage" class="page-content" style="display: none;">
<h1 class="page-title">Veri Yedekleme</h1>
<div class="settings-section">
<h2>🗄️ Veri Yedekleme</h2>
<p class="description">Tüm verilerinizi yedekleyebilir veya mevcut yedekleri geri yükleyebilirsiniz.</p>
<div class="backup-actions">
<button onclick="createBackup()" class="export-btn">💾 Veri Yedeği Oluştur</button>
<button onclick="document.getElementById('restoreFile').click()" class="add-btn">📥 Yedekten Geri Yükle</button>
<input type="file" id="restoreFile" accept=".json" style="display: none;" onchange="restoreBackup(event)">
</div>
<div id="backupStatus" class="status-message" style="display: none;"></div>
</div>
<div class="settings-section">
<h2>📊 Sunucu Durumu</h2>
<div id="serverStatus">
<div class="loading-spinner"></div>
<span>Sunucu durumu kontrol ediliyor...</span>
</div>
</div>
</div>
</div>
<button onclick="calculateAndAddPos()">Hesapla ve Poz Ekle</button>
<!-- Poz Listesi Bölümü -->
<div class="pos-list" id="posListContainer">
<h3>Hesaplanan Pozlar</h3>
<div class="storage-info" style="margin-bottom: 15px; padding: 10px; background: #f0f7ff; border-radius: 6px;">
<small>Poz Sayısı: <span id="posCount">0</span> |
Tahmini Depolama: <span id="storageSize">0 KB</span></small>
</div>
<div id="posList"></div>
<div class="input-row">
<button class="pdf-btn" onclick="generateAllPosPDF()">Tüm Pozları PDF Oluştur</button>
<button class="save-btn" onclick="saveAllPos()">Hesaplanan Pozları Kaydet</button>
<button class="delete-btn" onclick="clearPosList()">Poz Listesini Temizle</button>
</div>
</div>
</div>
<div id="settingsPage" style="display: none;">
<h1>Ayarlar</h1>
<!-- Veritabanı Kurulum Bölümü -->
<div class="settings-section" id="databaseSetupSection" style="display: block;">
<h2>🗄️ Veritabanı Kurulumu</h2>
<div id="databaseStatus" style="margin-bottom: 15px; padding: 15px; background: #f0f7ff; border-radius: 8px; border: 1px solid #4a90e2;">
<div id="dbStatusContent">
<h4 style="color: #2b6cb0; margin-bottom: 10px;">Veritabanı Durumu Kontrol Ediliyor...</h4>
<div style="display: flex; align-items: center; gap: 10px;">
<div class="loading-spinner" style="width: 20px; height: 20px; border: 2px solid #e2e8f0; border-top: 2px solid #4a90e2; border-radius: 50%; animation: spin 1s linear infinite;"></div>
<span>Veritabanı tabloları oluşturuluyor...</span>
</div>
</div>
</div>
<div class="input-row">
<button class="add-btn" onclick="setupDatabase()">🚀 Veritabanını Kur</button>
<button class="edit-btn" onclick="checkDatabaseStatus()">📊 Durumu Kontrol Et</button>
<button class="delete-btn" onclick="resetDatabase()">🔄 Veritabanını Sıfırla</button>
</div>
<div class="database-info" style="margin-top: 15px; padding: 15px; background: #fffaf0; border-radius: 6px; border: 1px solid #fbd38d;">
<h4 style="color: #d69e2e; margin-bottom: 10px;">📋 Veritabanı Tabloları</h4>
<div id="databaseTables" style="font-size: 12px; color: #718096;">
<div><strong>Sistemler:</strong> pencere_sistemleri (id, name, image, created_at)</div>
<div><strong>Profiller:</strong> sistem_profilleri (id, sistem_id, name, type, quantity, reduction, description, image, formula, created_at)</div>
<div><strong>Cariler:</strong> cariler (id, name, phone, email, address, created_at)</div>
<div><strong>Pozlar:</strong> pozlar (id, cari_id, sistem_id, project_name, width, height, quantity, horizontal_parts, vertical_parts, glass_parts, glass_info, created_at)</div>
<div><strong>Firma Bilgileri:</strong> firma_bilgileri (id, name, logo, address, phone, email, website, description, created_at)</div>
<div><strong>PDF Ayarları:</strong> pdf_ayarlar (id, settings_json, created_at, updated_at)</div>
</div>
</div>
</div>
<div class="settings-section">
<h2>Firma Bilgileri</h2>
<div class="input-row">
<button class="add-btn" onclick="openCompanyInfoModal()">🏢 Firma Bilgilerini Düzenle</button>
<button class="catalog-btn" onclick="previewCompanyInfo()">👁️ Önizleme</button>
</div>
<div class="company-info-preview" id="companyInfoPreview" style="margin-top: 15px; padding: 15px; background: #f8fafc; border-radius: 8px; display: none;">
<div class="company-info-display">
<div class="company-logo" id="companyLogoDisplay"></div>
<div class="company-details">
<h4 id="companyNameDisplay">Firma Adı</h4>
<p id="companyAddressDisplay">Firma Adresi</p>
<p id="companyContactDisplay">İletişim Bilgileri</p>
<p id="companyWebsiteDisplay">Website</p>
<p id="companyDescriptionDisplay">Firma Açıklaması</p>
</div>
</div>
</div>
</div>
<div class="settings-section">
<h2>Sistem Yönetimi</h2>
<div class="input-row">
<button class="add-btn" onclick="openSystemModal()">+ Yeni Sistem Ekle</button>
<button class="catalog-btn" onclick="openSystemsModal()">📋 Mevcut Sistemleri Görüntüle</button>
</div>
</div>
<div class="settings-section">
<h2>PDF Ayarları</h2>
<div class="input-row">
<button class="add-btn" onclick="openPDFSettingsModal()">⚙️ PDF Ayarları</button>
<button class="preview-btn" onclick="previewPDFSettings()">👁️ PDF Önizleme</button>
<button class="reset-btn" onclick="resetPDFSettings()">🔄 Varsayılana Sıfırla</button>
</div>
<div class="pdf-settings-info" style="margin-top: 15px; padding: 15px; background: #fffaf0; border-radius: 6px; border: 1px solid #fbd38d;">
<h4 style="color: #d69e2e; margin-bottom: 10px;">📄 PDF Özelleştirme</h4>
<small style="color: #718096;">
PDF ayarları ile raporların görünümünü özelleştirebilirsiniz. Logo boyutu, firma bilgileri görünürlüğü, tablo boyutları ve daha fazlasını ayarlayabilirsiniz.
</small>
</div>
</div>
<div class="settings-section">
<h2>Veri Yönetimi</h2>
<div class="input-row">
<button class="export-btn" onclick="exportAllData()">Tüm Verileri Dışa Aktar</button>
<button class="add-btn" onclick="importData()">Veri İçe Aktar</button>
<button class="delete-btn" onclick="clearAllData()">Tüm Verileri Temizle</button>
</div>
<div class="storage-info" style="margin-top: 15px; padding: 15px; background: #f0f7ff; border-radius: 6px;">
<h4>Depolama Durumu</h4>
<div id="storageStatus"></div>
</div>
</div>
</div>
<div id="customerPage" style="display: none;">
<div class="customer-management">
<div class="customer-list">
<div class="customer-header">
<h3>Cari Listesi</h3>
<button class="add-btn" onclick="openAddCustomerModal()">+ Yeni Cari Ekle</button>
</div>
<div class="form-group">
<input type="text" id="customerSearch" placeholder="Cari ara..." onkeyup="filterCustomers()">
</div>
<div id="customerListContainer" style="max-height: 400px; overflow-y: auto;">
<div class="empty-state">Henüz cari yok</div>
</div>
</div>
<div class="customer-pos-section" style="flex: 2;">
<div class="customer-header">
<h3>Seçili Cari Poz Listesi</h3>
<div id="selectedCustomerInfo" style="display: none;">
<span id="selectedCustomerName"></span>
</div>
</div>
<div id="customerPosContainer" style="max-height: 600px; overflow-y: auto; background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px;">
<div class="empty-state">Cari seçmek için listeden bir cariye tıklayın</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bildirimler -->
<div id="saveNotification" class="notification success">
Pozlar başarıyla carilere kaydedildi!
</div>
<div id="storageWarning" class="notification warning">
Depolama alanı doluyor! Eski verileri temizleyin veya dışa aktarın.
</div>
<!-- Sistem Ekleme/Düzenleme Modal -->
<div id="systemModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="systemModalTitle">Yeni Sistem Ekle</h2>
<button class="close-btn" onclick="closeSystemModal()">&times;</button>
</div>
<div class="settings-section">
<div class="form-group">
<label>Sistem Adı:</label>
<input type="text" id="modalSystemName" placeholder="Sistem adı">
</div>
<div class="form-group">
<label>Sistem Resmi:</label>
<input type="file" id="modalSystemImage" accept="image/*" onchange="previewImage(this, 'modalSystemImagePreview')">
<small style="color: #718096;">Not: Büyük resimler depolama alanını hızla doldurabilir</small>
</div>
<div id="modalSystemImagePreview"></div>
<div class="input-row">
<button class="add-btn" id="modalSystemButton" onclick="saveSystemFromModal()">Sistemi Kaydet</button>
<button class="delete-btn" onclick="closeSystemModal()">İptal</button>
</div>
</div>
</div>
</div>
<!-- Mevcut Sistemler Modal -->
<div id="systemsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Mevcut Sistemler</h2>
<button class="close-btn" onclick="closeSystemsModal()">&times;</button>
</div>
<div class="settings-section">
<div id="systemsListContainer">
<div class="empty-state">Henüz sistem yok</div>
</div>
</div>
</div>
</div>
<!-- Profil Ekleme/Düzenleme Modal -->
<div id="profileModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="profileModalTitle">Profil Ekle</h2>
<button class="close-btn" onclick="closeProfileModal()">&times;</button>
</div>
<div class="settings-section">
<div class="form-group">
<label>Sistem Seçin:</label>
<select id="modalSystemSelect">
<option value="">Sistem Seçin</option>
</select>
</div>
<div class="form-group">
<label>Profil Tipı:</label>
<select id="modalPartType" onchange="toggleFormulaSections()">
<option value="yatay">Yatay Profil</option>
<option value="dikey">Dikey Profil</option>
<option value="cam">Cam</option>
</select>
</div>
<div class="form-group">
<label>Açıklama:</label>
<textarea id="modalPartDescription" class="part-description-input" placeholder="Profil açıklamasını buraya yazın... (Örn: 4-16-4 İzolasyon Cam)"></textarea>
</div>
<div class="input-row">
<div class="form-group">
<label>Adet:</label>
<input type="number" id="modalPartQuantity" value="1" min="1">
</div>
<div id="reductionField" class="form-group">
<label>Düşüm (mm):</label>
<input type="number" id="modalPartReduction" value="0" min="0">
</div>
</div>
<!-- Gelişmiş Formül Bölümü - Yatay ve Dikey Profiller için -->
<div id="advancedFormulaSection" class="advanced-formula-section" style="display: none;">
<h4>Gelişmiş Ölçü Formülü</h4>
<div class="formula-input-group">
<select id="modalAdvancedFormulaType" onchange="updateAdvancedFormulaDescription()">
<option value="fixed">Sabit Düşüm</option>
<option value="formula">Matematiksel Formül</option>
<option value="custom">Özel Formül</option>
</select>
<input type="text" id="modalAdvancedFormula" placeholder="Örn: width - 20" value="width - 20">
</div>
<div id="modalAdvancedFormulaDescription" class="formula-help">
Sabit düşüm: Genişlikten/Yükseklikten sabit bir değer çıkarılır. Örn: 1500mm pencere için 1500 - 20 = 1480mm profil
</div>
<div class="formula-examples">
<strong>Örnek Formüller:</strong>
<div class="formula-example">width - 20</div>
<div class="formula-example">width - (width * 0.01) - 10</div>
<div class="formula-example">Math.floor(width) - 15</div>
<div class="formula-example">(width + height) / 2 - 25</div>
</div>
<!-- Formül Önizleme Butonu ve Bölümü -->
<button type="button" class="toggle-section-btn" onclick="toggleSection('advancedFormulaPreview')">
📊 Formül Önizleme Göster/Gizle
</button>
<div id="advancedFormulaPreview" class="formula-preview">
<strong>Formül Önizleme:</strong><br>
<span id="advancedFormulaPreviewText">Profil Ölçüsü = width - 20</span>
</div>
<!-- Formül Test Butonu ve Bölümü -->
<button type="button" class="toggle-section-btn" onclick="toggleSection('advancedFormulaTest')">
🧪 Formül Testi Göster/Gizle
</button>
<div id="advancedFormulaTest" class="formula-test">
<h5>Formül Testi</h5>
<div class="input-row">
<div class="form-group">
<label>Test Genişlik (mm):</label>
<input type="number" id="advancedTestWidth" value="1500">
</div>
<div class="form-group">
<label>Test Yükseklik (mm):</label>
<input type="number" id="advancedTestHeight" value="1200">
</div>
</div>
<button onclick="testAdvancedFormula()">Test Et</button>
<div id="advancedFormulaTestResult" class="formula-test-result"></div>
</div>
</div>
<div class="form-group">
<label>Profil Resmi:</label>
<input type="file" id="modalPartImage" accept="image/*" onchange="previewImage(this, 'modalPartImagePreview')">
<small style="color: #718096;">Not: Büyük resimler depolama alanını hızla doldurabilir</small>
</div>
<div id="modalPartImagePreview"></div>
<!-- Cam Formül Bölümü -->
<div id="camFormulaSection" class="glass-formula-section" style="display: none;">
<h4>Cam Ölçü Formülleri</h4>
<div class="glass-formula-section">
<h5>Yatay Cam Formülü</h5>
<div class="formula-input-group">
<select id="modalGlassHorizontalFormulaType" onchange="updateFormulaDescription('horizontal')">
<option value="fixed">Sabit Düşüm</option>
<option value="formula">Matematiksel Formül</option>
<option value="custom">Özel Formül</option>
</select>
<input type="text" id="modalGlassHorizontalFormula" placeholder="Örn: width - 20" value="width - 20">
</div>
<div id="modalGlassHorizontalDescription" class="formula-description">
Sabit düşüm: Genişlikten sabit bir değer çıkarılır. Örn: 1500mm pencere için 1500 - 20 = 1480mm cam
</div>
</div>
<div class="glass-formula-section">
<h5>Dikey Cam Formülü</h5>
<div class="formula-input-group">
<select id="modalGlassVerticalFormulaType" onchange="updateFormulaDescription('vertical')">
<option value="fixed">Sabit Düşüm</option>
<option value="formula">Matematiksel Formül</option>
<option value="custom">Özel Formül</option>
</select>
<input type="text" id="modalGlassVerticalFormula" placeholder="Örn: height - 20" value="height - 20">
</div>
<div id="modalGlassVerticalDescription" class="formula-description">
Sabit düşüm: Yükseklikten sabit bir değer çıkarılır. Örn: 1200mm pencere için 1200 - 20 = 1180mm cam
</div>
</div>
<!-- Formül Önizleme Butonu ve Bölümü -->
<button type="button" class="toggle-section-btn" onclick="toggleSection('formulaPreview')">
📊 Formül Önizleme Göster/Gizle
</button>
<div id="formulaPreview" class="formula-preview">
<strong>Formül Önizleme:</strong><br>
<span id="formulaPreviewText">Cam Genişlik = width - 20<br>Cam Yükseklik = height - 20</span>
</div>
<!-- Formül Test Butonu ve Bölümü -->
<button type="button" class="toggle-section-btn" onclick="toggleSection('formulaTest')">
🧪 Formül Testi Göster/Gizle
</button>
<div id="formulaTest" class="formula-test">
<h5>Formül Testi</h5>
<div class="input-row">
<div class="form-group">
<label>Test Genişlik (mm):</label>
<input type="number" id="testWidth" value="1500">
</div>
<div class="form-group">
<label>Test Yükseklik (mm):</label>
<input type="number" id="testHeight" value="1200">
</div>
</div>
<button onclick="testGlassFormulas()">Test Et</button>
<div id="formulaTestResult" class="formula-test-result"></div>
</div>
</div>
<div class="input-row">
<button class="add-btn" id="modalProfileButton" onclick="addPartFromModal()">Ekle</button>
<button class="delete-btn" onclick="closeProfileModal()">İptal</button>
</div>
</div>
</div>
</div>
<!-- Yeni Cari Ekleme Modal -->
<div id="addCustomerModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Yeni Cari Ekle</h2>
<button class="close-btn" onclick="closeAddCustomerModal()">&times;</button>
</div>
<div class="settings-section">
<div class="customer-edit-form">
<div class="form-group">
<label>Cari Adı:</label>
<input type="text" id="modalNewCustomerName" placeholder="Cari adı">
</div>
<div class="input-row">
<div class="form-group">
<label>Telefon:</label>
<input type="text" id="modalNewCustomerPhone" placeholder="Telefon numarası">
</div>
<div class="form-group">
<label>E-posta:</label>
<input type="email" id="modalNewCustomerEmail" placeholder="E-posta adresi">
</div>
</div>
<div class="form-group">
<label>Adres:</label>
<textarea id="modalNewCustomerAddress" placeholder="Cari adresi" style="min-height: 80px; resize: vertical;"></textarea>
</div>
<div class="input-row">
<button class="add-btn" onclick="addNewCustomer()">Cari Ekle</button>
<button class="delete-btn" onclick="closeAddCustomerModal()">İptal</button>
</div>
</div>
</div>
</div>
</div>
<!-- Cari Düzenleme Modal -->
<div id="customerEditModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Cari Bilgilerini Düzenle</h2>
<button class="close-btn" onclick="closeCustomerEditModal()">&times;</button>
</div>
<div class="settings-section">
<div class="customer-edit-form">
<div class="form-group">
<label>Mevcut Cari Adı:</label>
<input type="text" id="editCustomerName" readonly style="background-color: #f8fafc; color: #718096;">
</div>
<div class="form-group">
<label>Yeni Cari Adı:</label>
<input type="text" id="newEditCustomerName" placeholder="Yeni cari adı">
</div>
<div class="input-row">
<div class="form-group">
<label>Telefon:</label>
<input type="text" id="editCustomerPhone" placeholder="Telefon numarası">
</div>
<div class="form-group">
<label>E-posta:</label>
<input type="email" id="editCustomerEmail" placeholder="E-posta adresi">
</div>
</div>
<div class="form-group">
<label>Adres:</label>
<textarea id="editCustomerAddress" placeholder="Cari adresi"></textarea>
</div>
<div class="input-row">
<div class="form-group">
<label>Oluşturma Tarihi:</label>
<input type="text" id="editCustomerCreatedDate" readonly style="background-color: #f8fafc; color: #718096;">
</div>
<div class="form-group">
<label>Poz Sayısı:</label>
<input type="text" id="editCustomerPosCount" readonly style="background-color: #f8fafc; color: #718096;">
</div>
</div>
<div class="form-group" style="padding: 15px; background: #fffaf0; border-radius: 6px; border: 1px solid #fbd38d;">
<h4 style="color: #d69e2e; margin-bottom: 10px;">⚠️ Dikkat</h4>
<small style="color: #718096;">
• Cari adını değiştirirseniz, tüm pozlar yeni isimle kaydedilecektir.<br>
• Mevcut pozlar etkilenmeyecektir.<br>
• Bu işlem geri alınamaz.
</small>
</div>
<div class="input-row">
<button class="save-btn" onclick="saveCustomerEdit()">Cari Bilgilerini Kaydet</button>
<button class="delete-btn" onclick="closeCustomerEditModal()">İptal</button>
</div>
</div>
</div>
</div>
</div>
<!-- Firma Bilgileri Modal -->
<div id="companyInfoModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>🏢 Firma Bilgileri</h2>
<button class="close-btn" onclick="closeCompanyInfoModal()">&times;</button>
</div>
<div class="settings-section">
<div class="company-edit-form">
<div class="form-group">
<label>Firma Adı:</label>
<input type="text" id="modalCompanyName" placeholder="Firma adı">
</div>
<div class="form-group">
<label>Firma Logosu:</label>
<div class="company-logo-upload">
<input type="file" id="modalCompanyLogo" accept="image/*" onchange="previewCompanyLogo(this)">
<div class="company-logo-preview" id="companyLogoPreview">
<div class="company-logo-placeholder">Logo seçilmedi</div>
</div>
</div>
<small style="color: #718096;">Not: Büyük resimler depolama alanını hızla doldurabilir</small>
</div>
<div class="form-group">
<label>Firma Adresi:</label>
<textarea id="modalCompanyAddress" placeholder="Firma adresi" style="min-height: 60px; resize: vertical;"></textarea>
</div>
<div class="input-row">
<div class="form-group">
<label>Telefon:</label>
<input type="text" id="modalCompanyPhone" placeholder="Telefon numarası">
</div>
<div class="form-group">
<label>E-posta:</label>
<input type="email" id="modalCompanyEmail" placeholder="E-posta adresi">
</div>
</div>
<div class="form-group">
<label>Website:</label>
<input type="url" id="modalCompanyWebsite" placeholder="Firma website adresi">
</div>
<div class="form-group">
<label>Firma Açıklaması:</label>
<textarea id="modalCompanyDescription" placeholder="Firma hakkında kısa açıklama" style="min-height: 80px; resize: vertical;"></textarea>
</div>
<div class="form-group">
<label>Oluşturma Tarihi:</label>
<input type="text" id="modalCompanyCreatedDate" readonly style="background-color: #f8fafc; color: #718096;">
</div>
<div class="form-group" style="padding: 15px; background: #f0f7ff; border-radius: 6px; border: 1px solid #4a90e2;">
<h4 style="color: #2b6cb0; margin-bottom: 10px;">ℹ️ Bilgi</h4>
<small style="color: #718096;">
• Firma bilgileri PDF raporlarının üst kısmında yer alacak.<br>
• Logo resim olarak PDF'e eklenecektir.<br>
• Bu bilgiler tüm raporlarda gösterilecektir.
</small>
</div>
<div class="input-row">
<button class="save-btn" onclick="saveCompanyInfo()">Firma Bilgilerini Kaydet</button>
<button class="delete-btn" onclick="closeCompanyInfoModal()">İptal</button>
</div>
</div>
</div>
</div>
</div>
<!-- Poz Düzenleme Modal -->
<div id="editPosModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Poz Düzenle</h2>
<button class="close-btn" onclick="closeEditPosModal()">&times;</button>
</div>
<div class="settings-section">
<div class="edit-pos-form">
<div class="input-row">
<div class="form-group">
<label>Yatay (mm):</label>
<input type="number" id="editWidth" placeholder="Genişlik">
</div>
<div class="form-group">
<label>Dikey (mm):</label>
<input type="number" id="editHeight" placeholder="Yükseklik">
</div>
<div class="form-group">
<label>Adet:</label>
<input type="number" id="editQuantity" value="1">
</div>
</div>
<div class="input-row">
<div class="form-group">
<label>Proje:</label>
<input type="text" id="editProjectName" placeholder="Proje adı">
</div>
<div class="form-group">
<label>Cari:</label>
<input type="text" id="editCustomerName" placeholder="Cari adı" list="customerList">
</div>
</div>
<div class="input-row">
<button class="save-btn" onclick="updatePos()">Güncelle</button>
<button class="delete-btn" onclick="closeEditPosModal()">İptal</button>
</div>
</div>
</div>
</div>
</div>
<!-- PDF Ayarları Modal -->
<div id="pdfSettingsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>⚙️ PDF Ayarları</h2>
<button class="close-btn" onclick="closePDFSettingsModal()">&times;</button>
</div>
<div class="settings-section">
<div class="pdf-settings-form">
<!-- Logo Ayarları -->
<div class="pdf-settings-group" style="margin-bottom: 25px;">
<h3 style="color: #2b6cb0; margin-bottom: 15px; border-bottom: 2px solid #4a90e2; padding-bottom: 5px;">📷 Logo Ayarları</h3>
<div class="input-row">
<div class="form-group">
<label>Logo Boyutu (mm):</label>
<input type="range" id="logoWidth" min="10" max="60" value="25" oninput="updateLogoPreview()">
<small style="color: #718096;">Genişlik: <span id="logoWidthValue">25</span>mm</small>
</div>
<div class="form-group">
<label>Logo Yüksekliği (mm):</label>
<input type="range" id="logoHeight" min="10" max="60" value="25" oninput="updateLogoPreview()">
<small style="color: #718096;">Yükseklik: <span id="logoHeightValue">25</span>mm</small>
</div>
</div>
<div class="form-group">
<label>Logo Konumu:</label>
<select id="logoPosition" onchange="updateLogoPreview()">
<option value="left">Sol Üst</option>
<option value="center">Orta Üst</option>
<option value="right">Sağ Üst</option>
</select>
</div>
<div class="form-group">
<label>Logo Üstten Mesafe (mm):</label>
<input type="range" id="logoTopMargin" min="0" max="30" value="15" oninput="updateLogoPreview()">
<small style="color: #718096;">Üstten mesafe: <span id="logoTopMarginValue">15</span>mm</small>
</div>
<div class="logo-preview" id="logoPreview" style="margin-top: 15px; padding: 10px; border: 1px solid #e2e8f0; border-radius: 6px; background: white;">
<small style="color: #718096;">Logo önizleme ayarları yüklendiğinde görünecek</small>
</div>
</div>
<!-- Pencere Resmi Ayarları -->
<div class="pdf-settings-group" style="margin-bottom: 25px;">
<h3 style="color: #2b6cb0; margin-bottom: 15px; border-bottom: 2px solid #4a90e2; padding-bottom: 5px;">🖼️ Pencere Resmi Ayarları</h3>
<div class="form-group">
<label><input type="checkbox" id="showWindowImage" checked> Pencere Resmini Göster</label>
</div>
<div class="input-row">
<div class="form-group">
<label>Pencere Resmi Genişlik (mm):</label>
<input type="range" id="windowImageWidth" min="20" max="120" value="60" oninput="updateWindowImagePreview()">
<small style="color: #718096;">Genişlik: <span id="windowImageWidthValue">60</span>mm</small>
</div>
<div class="form-group">
<label>Pencere Resmi Yükseklik (mm):</label>
<input type="range" id="windowImageHeight" min="20" max="120" value="60" oninput="updateWindowImagePreview()">
<small style="color: #718096;">Yükseklik: <span id="windowImageHeightValue">60</span>mm</small>
</div>
</div>
<div class="input-row">
<div class="form-group">
<label>Resim Sol Kenar Boşluğu (mm):</label>
<input type="range" id="windowImageLeftMargin" min="0" max="50" value="20" oninput="updateWindowImagePreview()">
<small style="color: #718096;">Soldan mesafe: <span id="windowImageLeftMarginValue">20</span>mm</small>
</div>
<div class="form-group">
<label>Resim Üst Kenar Boşluğu (mm):</label>
<input type="range" id="windowImageTopMargin" min="0" max="50" value="20" oninput="updateWindowImagePreview()">
<small style="color: #718096;">Üstten mesafe: <span id="windowImageTopMarginValue">20</span>mm</small>
</div>
</div>
<div class="input-row">
<div class="form-group">
<label>Ölçü Çizgisi Kalınlığı (mm):</label>
<input type="range" id="dimensionLineThickness" min="0.2" max="2" value="0.5" step="0.1" oninput="updateWindowImagePreview()">
<small style="color: #718096;">Çizgi kalınlığı: <span id="dimensionLineThicknessValue">0.5</span>mm</small>
</div>
<div class="form-group">
<label>Ölçü Yazı Boyutu (pt):</label>
<input type="range" id="dimensionTextSize" min="6" max="14" value="9" oninput="updateWindowImagePreview()">
<small style="color: #718096;">Yazı boyutu: <span id="dimensionTextSizeValue">9</span>pt</small>
</div>
</div>
<div class="form-group">
<label>Pencere Resmi Konumu:</label>
<select id="windowImagePosition" onchange="updateWindowImagePreview()">
<option value="left">Sol Taraf</option>
<option value="center">Merkez</option>
<option value="right">Sağ Taraf</option>
</select>
</div>
<div class="window-image-preview" id="windowImagePreview" style="margin-top: 15px; padding: 10px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; position: relative; height: 100px;">
<small style="color: #718096;">Pencere resmi önizleme ayarları yüklendiğinde görünecek</small>
</div>
</div>
<!-- Firma Bilgileri Görünürlüğü -->
<div class="pdf-settings-group" style="margin-bottom: 25px;">
<h3 style="color: #2b6cb0; margin-bottom: 15px; border-bottom: 2px solid #4a90e2; padding-bottom: 5px;">🏢 Firma Bilgileri Görünürlüğü</h3>
<div class="form-group">
<label><input type="checkbox" id="showCompanyName" checked> Firma Adını Göster</label>
</div>
<div class="form-group">
<label><input type="checkbox" id="showCompanyLogo" checked> Firma Logosunu Göster</label>
</div>
<div class="form-group">
<label><input type="checkbox" id="showCompanyAddress" checked> Firma Adresini Göster</label>
</div>
<div class="form-group">
<label><input type="checkbox" id="showCompanyPhone" checked> Telefon Numarasını Göster</label>
</div>
<div class="form-group">
<label><input type="checkbox" id="showCompanyEmail" checked> E-posta Adresini Göster</label>
</div>
<div class="form-group">
<label><input type="checkbox" id="showCompanyWebsite" checked> Website Adresini Göster</label>
</div>
<div class="form-group">
<label><input type="checkbox" id="showCompanyDescription" checked> Firma Açıklamasını Göster</label>
</div>
</div>
<!-- Tablo Ayarları -->
<div class="pdf-settings-group" style="margin-bottom: 25px;">
<h3 style="color: #2b6cb0; margin-bottom: 15px; border-bottom: 2px solid #4a90e2; padding-bottom: 5px;">📊 Tablo Ayarları</h3>
<div class="input-row">
<div class="form-group">
<label>Tablo Satır Yüksekliği (mm):</label>
<input type="range" id="tableRowHeight" min="3" max="10" value="5" oninput="updateTableSettings()">
<small style="color: #718096;">Satır yüksekliği: <span id="tableRowHeightValue">5</span>mm</small>
</div>
<div class="form-group">
<label>Tablo Kenar Boşluğu (mm):</label>
<input type="range" id="tableMargin" min="10" max="30" value="20" oninput="updateTableSettings()">
<small style="color: #718096;">Kenar boşluğu: <span id="tableMarginValue">20</span>mm</small>
</div>
</div>
<div class="input-row">
<div class="form-group">
<label>Açıklama Sütunu Genişliği (%):</label>
<input type="range" id="descriptionColumnWidth" min="40" max="80" value="60" oninput="updateTableSettings()">
<small style="color: #718096;">Açıklama sütunu: <span id="descriptionColumnWidthValue">60</span>%</small>
</div>
<div class="form-group">
<label>MM Sütunu Genişliği (%):</label>
<input type="range" id="mmColumnWidth" min="10" max="30" value="20" oninput="updateTableSettings()">
<small style="color: #718096;">MM sütunu: <span id="mmColumnWidthValue">20</span>%</small>
</div>
<div class="form-group">
<label>Adet Sütunu Genişliği (%):</label>
<input type="range" id="quantityColumnWidth" min="10" max="30" value="20" oninput="updateTableSettings()">
<small style="color: #718096;">Adet sütunu: <span id="quantityColumnWidthValue">20</span>%</small>
</div>
</div>
<div class="form-group">
<label>Tablo Başlık Konumu:</label>
<select id="tableHeaderPosition" onchange="updateTableSettings()">
<option value="top">Tablo Üstünde</option>
<option value="left">Tablo Solunda</option>
</select>
</div>
<div class="form-group">
<label><input type="checkbox" id="showTableBorders" checked> Tablo Kenarlıklarını Göster</label>
</div>
</div>
<!-- Çizgi ve Pozisyon Ayarları -->
<div class="pdf-settings-group" style="margin-bottom: 25px;">
<h3 style="color: #2b6cb0; margin-bottom: 15px; border-bottom: 2px solid #4a90e2; padding-bottom: 5px;">📏 Çizgi ve Pozisyon Ayarları</h3>
<div class="input-row">
<div class="form-group">
<label>Ayırıcı Çizgi Üstten Mesafe (mm):</label>
<input type="range" id="separatorTopMargin" min="30" max="60" value="35" oninput="updateSeparatorSettings()">
<small style="color: #718096;">Çizgi üstten: <span id="separatorTopMarginValue">35</span>mm</small>
</div>
<div class="form-group">
<label>Ayırıcı Çizgi Kalınlığı (mm):</label>
<input type="range" id="separatorThickness" min="0.5" max="3" value="1" step="0.5" oninput="updateSeparatorSettings()">
<small style="color: #718096;">Çizgi kalınlığı: <span id="separatorThicknessValue">1</span>mm</small>
</div>
</div>
<div class="form-group">
<label>Ayırıcı Çizgi Konumu:</label>
<select id="separatorPosition" onchange="updateSeparatorSettings()">
<option value="left">Sola Dayalı</option>
<option value="center">Ortalanmış</option>
<option value="right">Sağa Dayalı</option>
<option value="full">Tam Genişlik</option>
</select>
</div>
</div>
<!-- Yazı Boyutları -->
<div class="pdf-settings-group" style="margin-bottom: 25px;">
<h3 style="color: #2b6cb0; margin-bottom: 15px; border-bottom: 2px solid #4a90e2; padding-bottom: 5px;">📝 Yazı Boyutları</h3>
<div class="input-row">
<div class="form-group">
<label>Başlık Yazı Boyutu:</label>
<input type="range" id="headerFontSize" min="10" max="20" value="14" oninput="updateFontSizes()">
<small style="color: #718096;">Başlık boyutu: <span id="headerFontSizeValue">14</span>pt</small>
</div>
<div class="form-group">
<label>Alt Başlık Yazı Boyutu:</label>
<input type="range" id="subheaderFontSize" min="8" max="16" value="11" oninput="updateFontSizes()">
<small style="color: #718096;">Alt başlık boyutu: <span id="subheaderFontSizeValue">11</span>pt</small>
</div>
<div class="form-group">
<label>Normal Yazı Boyutu:</label>
<input type="range" id="normalFontSize" min="6" max="12" value="9" oninput="updateFontSizes()">
<small style="color: #718096;">Normal yazı boyutu: <span id="normalFontSizeValue">9</span>pt</small>
</div>
</div>
<div class="input-row">
<div class="form-group">
<label>Küçük Yazı Boyutu:</label>
<input type="range" id="smallFontSize" min="6" max="10" value="8" oninput="updateFontSizes()">
<small style="color: #718096;">Küçük yazı boyutu: <span id="smallFontSizeValue">8</span>pt</small>
</div>
<div class="form-group">
<label>Yazı Kalınlığı:</label>
<select id="fontWeight" onchange="updateFontSizes()">
<option value="normal">Normal</option>
<option value="bold" selected>Kalın</option>
</select>
</div>
</div>
</div>
<!-- Sayfa Bazlı PDF Ayarları -->
<div class="pdf-settings-group" style="margin-bottom: 25px;">
<h3 style="color: #2b6cb0; margin-bottom: 15px; border-bottom: 2px solid #4a90e2; padding-bottom: 5px;">📄 Sayfa Bazlı PDF Ayarları</h3>
<div class="form-group">
<label><input type="checkbox" id="usePerPageSettings"> Sayfa Bazlı Ayarlar Kullan</label>
<small style="color: #718096;">Her PDF sayfası için farklı ayarlar uygulamak istiyorsanız bu seçeneği işaretleyin</small>
</div>
<div id="perPageSettingsContainer" style="display: none;">
<div class="input-row">
<div class="form-group">
<label>Sayfa Türü Seçin:</label>
<select id="pageTypeSelector" onchange="loadPageTypeSettings()">
<option value="">Sayfa türü seçin...</option>
<option value="title">Başlık Sayfası</option>
<option value="header">Firma Bilgileri Sayfası</option>
<option value="content">İçerik Sayfası</option>
<option value="summary">Özet Sayfası</option>
</select>
</div>
<div class="form-group">
<label>Önceki/İleri Ayarlar:</label>
<div style="display: flex; gap: 5px;">
<button class="save-btn" onclick="saveCurrentPageSettings()" style="padding: 8px 12px; font-size: 12px;">Kaydet</button>
<button class="edit-btn" onclick="resetCurrentPageSettings()" style="padding: 8px 12px; font-size: 12px;">Sıfırla</button>
</div>
</div>
</div>
<div id="perPageSettingsList" style="margin-top: 15px;">
<h5 style="color: #4a5568; margin-bottom: 10px;">Kaydedilen Sayfa Ayarları:</h5>
<div id="pageSettingsListContainer"></div>
</div>
</div>
</div>
<div class="input-row">
<button class="save-btn" onclick="savePDFSettings()">💾 Global PDF Ayarlarını Kaydet</button>
<button class="preview-btn" onclick="previewPDFWithSettings()">👁️ Ayarlarla PDF Önizle</button>
<button class="delete-btn" onclick="closePDFSettingsModal()">İptal</button>
</div>
</div>
</div>
</div>
</div>
<script>
// jsPDF'yi global olarak tanımla
window.jsPDF = window.jspdf.jsPDF;
// Global değişkenler
let systems = [];
let customerData = {};
let positions = [];
let companyData = null;
let pdfSettings = {};
let perPageSettings = {};
let currentSystemId = null;
let currentPartId = null;
let calculationResults = {};
let isEditingSystem = false;
let isEditingPart = false;
let selectedCustomer = null;
let posList = [];
let editingPosIndex = null;
let editingCustomerPos = { customerName: null, pozNumber: null };
let selectedPositions = new Set();
let currentEditingCustomer = null;
// Varsayılan ayarları yükle
function loadDefaultSettings() {
pdfSettings = {
logoWidth: 25,
logoHeight: 25,
logoPosition: 'left',
logoTopMargin: 15,
showWindowImage: true,
windowImageWidth: 60,
windowImageHeight: 60,
windowImageLeftMargin: 20,
windowImageTopMargin: 20,
dimensionLineThickness: 0.5,
dimensionTextSize: 9,
windowImagePosition: 'left',
showCompanyName: true,
showCompanyLogo: true,
showCompanyAddress: true,
showCompanyPhone: true,
showCompanyEmail: true,
showCompanyWebsite: true,
showCompanyDescription: true,
tableRowHeight: 5,
tableMargin: 20,
descriptionColumnWidth: 60,
mmColumnWidth: 20,
quantityColumnWidth: 20,
tableHeaderPosition: 'top',
showTableBorders: true,
separatorTopMargin: 35,
separatorThickness: 1,
separatorPosition: 'full',
headerFontSize: 14,
subheaderFontSize: 11,
normalFontSize: 9,
smallFontSize: 8,
fontWeight: 'bold',
usePerPageSettings: false
};
companyData = {
name: 'Firma Adı',
logo: null,
address: '',
phone: '',
email: '',
website: '',
description: '',
createdAt: new Date().toISOString()
};
perPageSettings = {
enabled: false,
pages: {
title: {
name: 'Başlık Sayfası',
description: 'PDF başlık sayfası ayarları',
logoWidth: 40,
logoHeight: 40,
logoPosition: 'center',
logoTopMargin: 30,
headerFontSize: 18,
subheaderFontSize: 14,
normalFontSize: 10,
smallFontSize: 8,
separatorTopMargin: 50,
separatorThickness: 2
},
header: {
name: 'Firma Bilgileri Sayfası',
description: 'Firma bilgileri ve header ayarları',
logoWidth: 25,
logoHeight: 25,
logoPosition: 'left',
logoTopMargin: 15,
headerFontSize: 14,
subheaderFontSize: 11,
normalFontSize: 9,
smallFontSize: 8,
separatorTopMargin: 35,
separatorThickness: 1
},
content: {
name: 'İçerik Sayfası',
description: 'Ana içerik sayfaları için ayarlar',
logoWidth: 15,
logoHeight: 15,
logoPosition: 'right',
logoTopMargin: 10,
headerFontSize: 12,
subheaderFontSize: 10,
normalFontSize: 8,
smallFontSize: 7,
separatorTopMargin: 25,
separatorThickness: 0.5
},
summary: {
name: 'Özet Sayfası',
description: 'Özet ve sonuç sayfaları',
logoWidth: 20,
logoHeight: 20,
logoPosition: 'center',
logoTopMargin: 20,
headerFontSize: 16,
subheaderFontSize: 12,
normalFontSize: 9,
smallFontSize: 7,
separatorTopMargin: 40,
separatorThickness: 1.5
}
}
};
}
// Sayfa yüklendiğinde varsayılan ayarları yükle
loadDefaultSettings();
// Türkçe karakter düzeltme
function fixTurkishChars(text) {
if (!text) return '';
return text.toString()
.replace(/ı/g, 'i')
.replace(/İ/g, 'I')
.replace(/ğ/g, 'g')
.replace(/Ğ/g, 'G')
.replace(/ü/g, 'u')
.replace(/Ü/g, 'U')
.replace(/ş/g, 's')
.replace(/Ş/g, 'S')
.replace(/ö/g, 'o')
.replace(/Ö/g, 'O')
.replace(/ç/g, 'c')
.replace(/Ç/g, 'C');
}
// PDF ayarları fonksiyonları
function openPDFSettingsModal() {
$('pdfSettingsModal').style.display = 'block';
// Mevcut ayarları modal'a yükle
loadPDFSettings();
// Sayfa bazlı ayarlar durumunu güncelle
updatePerPageSettingsVisibility();
updateAllPreviews();
}
function loadPDFSettings() {
// Ana PDF ayarları
$('logoWidth').value = pdfSettings.logoWidth;
$('logoWidthValue').textContent = pdfSettings.logoWidth;
$('logoHeight').value = pdfSettings.logoHeight;
$('logoHeightValue').textContent = pdfSettings.logoHeight;
$('logoPosition').value = pdfSettings.logoPosition;
$('logoTopMargin').value = pdfSettings.logoTopMargin;
$('logoTopMarginValue').textContent = pdfSettings.logoTopMargin;
$('showWindowImage').checked = pdfSettings.showWindowImage;
$('windowImageWidth').value = pdfSettings.windowImageWidth;
$('windowImageWidthValue').textContent = pdfSettings.windowImageWidth;
$('windowImageHeight').value = pdfSettings.windowImageHeight;
$('windowImageHeightValue').textContent = pdfSettings.windowImageHeight;
$('windowImageLeftMargin').value = pdfSettings.windowImageLeftMargin;
$('windowImageLeftMarginValue').textContent = pdfSettings.windowImageLeftMargin;
$('windowImageTopMargin').value = pdfSettings.windowImageTopMargin;
$('windowImageTopMarginValue').textContent = pdfSettings.windowImageTopMargin;
$('dimensionLineThickness').value = pdfSettings.dimensionLineThickness;
$('dimensionLineThicknessValue').textContent = pdfSettings.dimensionLineThickness;
$('dimensionTextSize').value = pdfSettings.dimensionTextSize;
$('dimensionTextSizeValue').textContent = pdfSettings.dimensionTextSize;
$('windowImagePosition').value = pdfSettings.windowImagePosition;
$('showCompanyName').checked = pdfSettings.showCompanyName;
$('showCompanyLogo').checked = pdfSettings.showCompanyLogo;
$('showCompanyAddress').checked = pdfSettings.showCompanyAddress;
$('showCompanyPhone').checked = pdfSettings.showCompanyPhone;
$('showCompanyEmail').checked = pdfSettings.showCompanyEmail;
$('showCompanyWebsite').checked = pdfSettings.showCompanyWebsite;
$('showCompanyDescription').checked = pdfSettings.showCompanyDescription;
$('tableRowHeight').value = pdfSettings.tableRowHeight;
$('tableRowHeightValue').textContent = pdfSettings.tableRowHeight;
$('tableMargin').value = pdfSettings.tableMargin;
$('tableMarginValue').textContent = pdfSettings.tableMargin;
$('descriptionColumnWidth').value = pdfSettings.descriptionColumnWidth;
$('descriptionColumnWidthValue').textContent = pdfSettings.descriptionColumnWidth;
$('mmColumnWidth').value = pdfSettings.mmColumnWidth;
$('mmColumnWidthValue').textContent = pdfSettings.mmColumnWidth;
$('quantityColumnWidth').value = pdfSettings.quantityColumnWidth;
$('quantityColumnWidthValue').textContent = pdfSettings.quantityColumnWidth;
$('tableHeaderPosition').value = pdfSettings.tableHeaderPosition;
$('showTableBorders').checked = pdfSettings.showTableBorders;
$('separatorTopMargin').value = pdfSettings.separatorTopMargin;
$('separatorTopMarginValue').textContent = pdfSettings.separatorTopMargin;
$('separatorThickness').value = pdfSettings.separatorThickness;
$('separatorThicknessValue').textContent = pdfSettings.separatorThickness;
$('separatorPosition').value = pdfSettings.separatorPosition;
$('headerFontSize').value = pdfSettings.headerFontSize;
$('headerFontSizeValue').textContent = pdfSettings.headerFontSize;
$('subheaderFontSize').value = pdfSettings.subheaderFontSize;
$('subheaderFontSizeValue').textContent = pdfSettings.subheaderFontSize;
$('normalFontSize').value = pdfSettings.normalFontSize;
$('normalFontSizeValue').textContent = pdfSettings.normalFontSize;
$('smallFontSize').value = pdfSettings.smallFontSize;
$('smallFontSizeValue').textContent = pdfSettings.smallFontSize;
$('fontWeight').value = pdfSettings.fontWeight;
// Sayfa bazlı ayarlar durumu
$('usePerPageSettings').checked = pdfSettings.usePerPageSettings;
}
function closePDFSettingsModal() {
$('pdfSettingsModal').style.display = 'none';
}
function updateLogoPreview() {
$('logoWidthValue').textContent = $('logoWidth').value;
$('logoHeightValue').textContent = $('logoHeight').value;
$('logoTopMarginValue').textContent = $('logoTopMargin').value;
updateLogoPreviewDisplay();
}
function updateLogoPreviewDisplay() {
const preview = $('logoPreview');
const logoData = companyData.logo;
if (!logoData) {
preview.innerHTML = '<div style="color: #a0aec0; font-size: 12px; text-align: center; padding: 20px;">Logo mevcut değil</div>';
return;
}
const width = $('logoWidth').value;
const height = $('logoHeight').value;
const position = $('logoPosition').value;
const topMargin = $('logoTopMargin').value;
const positionStyles = {
left: 'margin-left: 0;',
center: 'margin: 0 auto; display: block;',
right: 'margin-left: auto; display: block;'
};
preview.innerHTML = `
<div style="height: ${parseInt(topMargin) + 30}px; border-bottom: 1px solid #e2e8f0; margin-bottom: 10px;">
<img src="${logoData}" style="width: ${width}px; height: ${height}px; object-fit: contain; ${positionStyles[position] || positionStyles.left}">
</div>
<small style="color: #718096;">
Genişlik: ${width}px, Yükseklik: ${height}px, Konum: ${position === 'left' ? 'Sol' : position === 'center' ? 'Orta' : 'Sağ'}, Üstten: ${topMargin}px
</small>
`;
}
function updateTableSettings() {
$('tableRowHeightValue').textContent = $('tableRowHeight').value;
$('tableMarginValue').textContent = $('tableMargin').value;
$('descriptionColumnWidthValue').textContent = $('descriptionColumnWidth').value;
$('mmColumnWidthValue').textContent = $('mmColumnWidth').value;
$('quantityColumnWidthValue').textContent = $('quantityColumnWidth').value;
updateTablePreview();
}
function updateTablePreview() {
const rowHeight = $('tableRowHeight').value;
const descWidth = $('descriptionColumnWidth').value;
const mmWidth = $('mmColumnWidth').value;
const qtyWidth = $('quantityColumnWidth').value;
const preview = `
<div style="border: ${$('showTableBorders').checked ? '1px solid #333' : 'none'}; margin: 10px 0;">
<div style="display: flex; height: ${rowHeight * 3}px; border-bottom: 1px solid #333; background: #f8f8f8; font-weight: bold; font-size: 10px;">
<div style="width: ${descWidth}%; border-right: 1px solid #333; display: flex; align-items: center; padding: 0 5px;">AÇIKLAMA</div>
<div style="width: ${mmWidth}%; border-right: 1px solid #333; display: flex; align-items: center; padding: 0 5px;">MM</div>
<div style="width: ${qtyWidth}%; display: flex; align-items: center; padding: 0 5px;">ADET</div>
</div>
<div style="display: flex; height: ${rowHeight * 3}px; font-size: 9px;">
<div style="width: ${descWidth}%; border-right: 1px solid #333; display: flex; align-items: center; padding: 0 5px;">Örnek Profil</div>
<div style="width: ${mmWidth}%; border-right: 1px solid #333; display: flex; align-items: center; padding: 0 5px;">1500</div>
<div style="width: ${qtyWidth}%; display: flex; align-items: center; padding: 0 5px;">2</div>
</div>
</div>
<small style="color: #718096;">
Satır yüksekliği: ${rowHeight}mm | Sütun oranları: ${descWidth}%, ${mmWidth}%, ${qtyWidth}%
</small>
`;
if (!$('tablePreview')) {
const tablePreviewDiv = document.createElement('div');
tablePreviewDiv.id = 'tablePreview';
tablePreviewDiv.style.marginTop = '10px';
tablePreviewDiv.style.padding = '10px';
tablePreviewDiv.style.border = '1px solid #e2e8f0';
tablePreviewDiv.style.borderRadius = '6px';
tablePreviewDiv.style.background = 'white';
tablePreviewDiv.innerHTML = preview;
document.getElementById('pdfSettingsModal').appendChild(tablePreviewDiv);
} else {
document.getElementById('tablePreview').innerHTML = preview;
}
}
function updateSeparatorSettings() {
$('separatorTopMarginValue').textContent = $('separatorTopMargin').value;
$('separatorThicknessValue').textContent = $('separatorThickness').value;
updateSeparatorPreview();
}
function updateSeparatorPreview() {
const topMargin = $('separatorTopMargin').value;
const thickness = $('separatorThickness').value;
const position = $('separatorPosition').value;
let lineStyle = '';
switch(position) {
case 'left':
lineStyle = 'margin-left: 0; width: 30%;';
break;
case 'center':
lineStyle = 'margin: 0 auto; width: 30%;';
break;
case 'right':
lineStyle = 'margin-left: auto; margin-right: 0; width: 30%;';
break;
case 'full':
lineStyle = 'width: 100%;';
break;
}
const preview = `
<div style="height: ${topMargin}px; border-bottom: 1px dashed #e2e8f0; margin-bottom: 10px; display: flex; align-items: end;">
<div style="height: ${thickness}px; background: #333; ${lineStyle}"></div>
</div>
<small style="color: #718096;">
Ayırıcı çizgi: ${thickness}mm kalınlık, ${position === 'left' ? 'sol' : position === 'center' ? 'orta' : position === 'right' ? 'sağ' : 'tam genişlik'} konum
</small>
`;
if (!$('separatorPreview')) {
const separatorPreviewDiv = document.createElement('div');
separatorPreviewDiv.id = 'separatorPreview';
separatorPreviewDiv.style.marginTop = '10px';
separatorPreviewDiv.style.padding = '10px';
separatorPreviewDiv.style.border = '1px solid #e2e8f0';
separatorPreviewDiv.style.borderRadius = '6px';
separatorPreviewDiv.style.background = 'white';
separatorPreviewDiv.innerHTML = preview;
document.getElementById('pdfSettingsModal').appendChild(separatorPreviewDiv);
} else {
document.getElementById('separatorPreview').innerHTML = preview;
}
}
function updateFontSizes() {
$('headerFontSizeValue').textContent = $('headerFontSize').value;
$('subheaderFontSizeValue').textContent = $('subheaderFontSize').value;
$('normalFontSizeValue').textContent = $('normalFontSize').value;
$('smallFontSizeValue').textContent = $('smallFontSize').value;
updateFontPreview();
}
function updateFontPreview() {
const headerSize = $('headerFontSize').value;
const subheaderSize = $('subheaderFontSize').value;
const normalSize = $('normalFontSize').value;
const smallSize = $('smallFontSize').value;
const fontWeight = $('fontWeight').value;
const preview = `
<div style="padding: 10px; line-height: 1.4;">
<div style="font-size: ${headerSize}pt; font-weight: ${fontWeight === 'bold' ? 'bold' : 'normal'}; margin-bottom: 5px;">
Başlık Örneği (${headerSize}pt)
</div>
<div style="font-size: ${subheaderSize}pt; font-weight: ${fontWeight === 'bold' ? 'bold' : 'normal'}; margin-bottom: 5px;">
Alt Başlık Örneği (${subheaderSize}pt)
</div>
<div style="font-size: ${normalSize}pt; margin-bottom: 5px;">
Normal Yazı Örneği (${normalSize}pt)
</div>
<div style="font-size: ${smallSize}pt;">
Küçük Yazı Örneği (${smallSize}pt)
</div>
</div>
<small style="color: #718096;">
Kalınlık: ${fontWeight === 'bold' ? 'Kalın' : 'Normal'}
</small>
`;
if (!$('fontPreview')) {
const fontPreviewDiv = document.createElement('div');
fontPreviewDiv.id = 'fontPreview';
fontPreviewDiv.style.marginTop = '10px';
fontPreviewDiv.style.padding = '10px';
fontPreviewDiv.style.border = '1px solid #e2e8f0';
fontPreviewDiv.style.borderRadius = '6px';
fontPreviewDiv.style.background = 'white';
fontPreviewDiv.innerHTML = preview;
document.getElementById('pdfSettingsModal').appendChild(fontPreviewDiv);
} else {
document.getElementById('fontPreview').innerHTML = preview;
}
}
function updateAllPreviews() {
updateLogoPreviewDisplay();
updateTablePreview();
updateSeparatorPreview();
updateFontPreview();
}
function savePDFSettings() {
pdfSettings = {
// Logo ayarları
logoWidth: parseInt($('logoWidth').value),
logoHeight: parseInt($('logoHeight').value),
logoPosition: $('logoPosition').value,
logoTopMargin: parseInt($('logoTopMargin').value),
// Pencere resmi ayarları
showWindowImage: $('showWindowImage').checked,
windowImageWidth: parseInt($('windowImageWidth').value),
windowImageHeight: parseInt($('windowImageHeight').value),
windowImageLeftMargin: parseInt($('windowImageLeftMargin').value),
windowImageTopMargin: parseInt($('windowImageTopMargin').value),
dimensionLineThickness: parseFloat($('dimensionLineThickness').value),
dimensionTextSize: parseInt($('dimensionTextSize').value),
windowImagePosition: $('windowImagePosition').value,
// Firma bilgileri görünürlüğü
showCompanyName: $('showCompanyName').checked,
showCompanyLogo: $('showCompanyLogo').checked,
showCompanyAddress: $('showCompanyAddress').checked,
showCompanyPhone: $('showCompanyPhone').checked,
showCompanyEmail: $('showCompanyEmail').checked,
showCompanyWebsite: $('showCompanyWebsite').checked,
showCompanyDescription: $('showCompanyDescription').checked,
// Tablo ayarları
tableRowHeight: parseInt($('tableRowHeight').value),
tableMargin: parseInt($('tableMargin').value),
descriptionColumnWidth: parseInt($('descriptionColumnWidth').value),
mmColumnWidth: parseInt($('mmColumnWidth').value),
quantityColumnWidth: parseInt($('quantityColumnWidth').value),
tableHeaderPosition: $('tableHeaderPosition').value,
showTableBorders: $('showTableBorders').checked,
// Çizgi ayarları
separatorTopMargin: parseInt($('separatorTopMargin').value),
separatorThickness: parseFloat($('separatorThickness').value),
separatorPosition: $('separatorPosition').value,
// Yazı boyutları
headerFontSize: parseInt($('headerFontSize').value),
subheaderFontSize: parseInt($('subheaderFontSize').value),
normalFontSize: parseInt($('normalFontSize').value),
smallFontSize: parseInt($('smallFontSize').value),
fontWeight: $('fontWeight').value
};
try {
localStorage.setItem('pdfSettings', JSON.stringify(pdfSettings));
updateStorageStatus();
closePDFSettingsModal();
alert('PDF ayarları başarıyla kaydedildi!');
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('Depolama alanı doldu! Ayarlar kaydedilemedi.');
}
}
}
function resetPDFSettings() {
if (!confirm('PDF ayarlarını varsayılan değerlere sıfırlamak istediğinizden emin misiniz?')) return;
pdfSettings = {
// Logo ayarları
logoWidth: 25,
logoHeight: 25,
logoPosition: 'left',
logoTopMargin: 15,
// Pencere resmi ayarları
showWindowImage: true,
windowImageWidth: 60,
windowImageHeight: 60,
windowImageLeftMargin: 20,
windowImageTopMargin: 20,
dimensionLineThickness: 0.5,
dimensionTextSize: 9,
windowImagePosition: 'left',
// Firma bilgileri görünürlüğü
showCompanyName: true,
showCompanyLogo: true,
showCompanyAddress: true,
showCompanyPhone: true,
showCompanyEmail: true,
showCompanyWebsite: true,
showCompanyDescription: true,
// Tablo ayarları
tableRowHeight: 5,
tableMargin: 20,
descriptionColumnWidth: 60,
mmColumnWidth: 20,
quantityColumnWidth: 20,
tableHeaderPosition: 'top',
showTableBorders: true,
// Çizgi ayarları
separatorTopMargin: 35,
separatorThickness: 1,
separatorPosition: 'full',
// Yazı boyutları
headerFontSize: 14,
subheaderFontSize: 11,
normalFontSize: 9,
smallFontSize: 8,
fontWeight: 'bold'
};
try {
localStorage.setItem('pdfSettings', JSON.stringify(pdfSettings));
updateStorageStatus();
openPDFSettingsModal();
alert('PDF ayarları varsayılan değerlere sıfırlandı!');
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('Depolama alanı doldu! Ayarlar sıfırlanamadı.');
}
}
}
function previewPDFSettings() {
// Mevcut ayarları gösteren bir modal
const previewWindow = window.open('', '_blank', 'width=800,height=600');
const previewHTML = `
<html>
<head>
<title>PDF Ayarları Önizleme</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.setting-group { margin-bottom: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
.setting-title { font-weight: bold; color: #333; border-bottom: 1px solid #ccc; padding-bottom: 5px; margin-bottom: 10px; }
.setting-item { margin: 5px 0; }
.value { color: #0066cc; font-weight: bold; }
</style>
</head>
<body>
<h1>PDF Ayarları Önizleme</h1>
<div class="setting-group">
<div class="setting-title">📷 Logo Ayarları</div>
<div class="setting-item">Logo Boyutu: <span class="value">${pdfSettings.logoWidth} x ${pdfSettings.logoHeight}mm</span></div>
<div class="setting-item">Logo Konumu: <span class="value">${pdfSettings.logoPosition === 'left' ? 'Sol Üst' : pdfSettings.logoPosition === 'center' ? 'Orta Üst' : 'Sağ Üst'}</span></div>
<div class="setting-item">Logo Üstten Mesafe: <span class="value">${pdfSettings.logoTopMargin}mm</span></div>
</div>
<div class="setting-group">
<div class="setting-title">🏢 Firma Bilgileri Görünürlüğü</div>
<div class="setting-item">Firma Adı: <span class="value">${pdfSettings.showCompanyName ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
<div class="setting-item">Firma Logosu: <span class="value">${pdfSettings.showCompanyLogo ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
<div class="setting-item">Firma Adresi: <span class="value">${pdfSettings.showCompanyAddress ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
<div class="setting-item">Telefon: <span class="value">${pdfSettings.showCompanyPhone ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
<div class="setting-item">E-posta: <span class="value">${pdfSettings.showCompanyEmail ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
<div class="setting-item">Website: <span class="value">${pdfSettings.showCompanyWebsite ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
<div class="setting-item">Firma Açıklaması: <span class="value">${pdfSettings.showCompanyDescription ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
</div>
<div class="setting-group">
<div class="setting-title">📊 Tablo Ayarları</div>
<div class="setting-item">Satır Yüksekliği: <span class="value">${pdfSettings.tableRowHeight}mm</span></div>
<div class="setting-item">Kenar Boşluğu: <span class="value">${pdfSettings.tableMargin}mm</span></div>
<div class="setting-item">Sütun Genişlikleri: <span class="value">${pdfSettings.descriptionColumnWidth}% | ${pdfSettings.mmColumnWidth}% | ${pdfSettings.quantityColumnWidth}%</span></div>
<div class="setting-item">Başlık Konumu: <span class="value">${pdfSettings.tableHeaderPosition === 'top' ? 'Tablo Üstünde' : 'Tablo Solunda'}</span></div>
<div class="setting-item">Kenar Çizgileri: <span class="value">${pdfSettings.showTableBorders ? '✅ Gösterilecek' : '❌ Gizlenecek'}</span></div>
</div>
<div class="setting-group">
<div class="setting-title">📏 Çizgi ve Pozisyon Ayarları</div>
<div class="setting-item">Ayırıcı Çizgi Üstten Mesafe: <span class="value">${pdfSettings.separatorTopMargin}mm</span></div>
<div class="setting-item">Çizgi Kalınlığı: <span class="value">${pdfSettings.separatorThickness}mm</span></div>
<div class="setting-item">Çizgi Konumu: <span class="value">${pdfSettings.separatorPosition === 'left' ? 'Sol Dayalı' : pdfSettings.separatorPosition === 'center' ? 'Ortalanmış' : pdfSettings.separatorPosition === 'right' ? 'Sağ Dayalı' : 'Tam Genişlik'}</span></div>
</div>
<div class="setting-group">
<div class="setting-title">📝 Yazı Boyutları</div>
<div class="setting-item">Başlık: <span class="value">${pdfSettings.headerFontSize}pt</span></div>
<div class="setting-item">Alt Başlık: <span class="value">${pdfSettings.subheaderFontSize}pt</span></div>
<div class="setting-item">Normal Yazı: <span class="value">${pdfSettings.normalFontSize}pt</span></div>
<div class="setting-item">Küçük Yazı: <span class="value">${pdfSettings.smallFontSize}pt</span></div>
<div class="setting-item">Yazı Kalınlığı: <span class="value">${pdfSettings.fontWeight === 'bold' ? 'Kalın' : 'Normal'}</span></div>
</div>
</body>
</html>
`;
previewWindow.document.write(previewHTML);
previewWindow.document.close();
}
function previewPDFWithSettings() {
if (posList.length === 0) {
alert('PDF önizlemesi oluşturmak için en az bir poz olmalıdır!');
return;
}
// Çok sayfalı PDF önizlemesi oluştur
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
setupPDFFont(doc);
let currentPage = 1;
const totalPages = 3; // Örnek 3 sayfalık PDF
// Sayfa bazlı ayarları gösteren bir özel PDF oluştur
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
if (pageNum > 1) {
doc.addPage();
}
const isFirstPage = pageNum === 1;
const pageType = determinePageType(pageNum, totalPages, pageNum, isFirstPage);
const settings = getSettingsForPageType(pageType);
// Sayfa tipi başlığı
doc.setFontSize(settings.headerFontSize);
doc.setFont(undefined, settings.fontWeight);
doc.text(`Sayfa ${pageNum} - ${perPageSettings.pages[pageType]?.name || 'Global Ayarlar'}`, 105, 20, { align: 'center' });
// Kullanılan ayarları göster
doc.setFontSize(settings.normalFontSize);
doc.setFont(undefined, 'normal');
const settingsInfo = [
`Sayfa Tipi: ${perPageSettings.pages[pageType]?.name || 'Global Ayarlar'}`,
`Logo: ${settings.logoWidth}x${settings.logoHeight}mm (${settings.logoPosition})`,
`Yazı Boyutları: Başlık ${settings.headerFontSize}pt, Normal ${settings.normalFontSize}pt`,
`Çizgi: ${settings.separatorThickness}mm kalın, ${settings.separatorTopMargin}mm üstten`,
`Üstten Mesafe: ${settings.logoTopMargin}mm`
];
let yPos = 40;
settingsInfo.forEach(info => {
doc.text(fixTurkishChars(info), 20, yPos);
yPos += 8;
});
// Örnek içerik
yPos += 10;
doc.setFontSize(settings.subheaderFontSize);
doc.setFont(undefined, settings.fontWeight);
doc.text('Örnek İçerik', 20, yPos);
yPos += 10;
doc.setFontSize(settings.normalFontSize);
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars('Bu sayfa için ayarlar örnek olarak gösterilmektedir.'), 20, yPos);
// Örnek logo konumu gösterimi
if (companyData.logo) {
try {
const imgWidth = Math.min(settings.logoWidth, 30);
const imgHeight = Math.min(settings.logoHeight, 30);
let logoX = 20;
switch(settings.logoPosition) {
case 'center':
logoX = 105 - (imgWidth / 2);
break;
case 'right':
logoX = 170;
break;
}
doc.text('Logo Konumu Örneği:', 20, yPos + 20);
doc.addImage(companyData.logo, 'JPEG', logoX, yPos + 25, imgWidth, imgHeight);
} catch (error) {
console.error('Örnek logo PDF\'e eklenemedi:', error);
}
}
}
doc.save('sayfa-bazli-ayarlar-onizleme.pdf');
}
function savePDFSettingsData() {
try {
localStorage.setItem('pdfSettings', JSON.stringify(pdfSettings));
localStorage.setItem('perPageSettings', JSON.stringify(perPageSettings));
updateStorageStatus();
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('Depolama alanı doldu! PDF ayarları kaydedilemedi.');
return false;
}
throw e;
}
}
// Temel fonksiyonlar
const $ = id => document.getElementById(id);
const showPage = pageId => {
['mainPage','settingsPage','customerPage','backupPage'].forEach(p => {
const element = $(p);
if (element) {
element.style.display = p === pageId ? 'block' : 'none';
}
});
if (pageId === 'mainPage') {
updateNewCustomerButton();
updatePosList();
updateStorageInfo();
}
if (pageId === 'customerPage') {
selectedCustomer = null;
const container = $('customerPosContainer');
if (container) {
container.innerHTML = '<div class="empty-state">Cari seçmek için listeden bir cariye tıklayın</div>';
}
selectedPositions.clear();
}
if (pageId === 'settingsPage') {
updateStorageStatus();
}
if (pageId === 'backupPage') {
updateServerStatus();
}
};
document.addEventListener('DOMContentLoaded', async () => {
try {
// Check authentication
if (!api.isAuthenticated()) {
window.location.href = 'login.html';
return;
}
// Update user info
updateUserInfo();
// Load initial data
await loadInitialData();
// Update UI components
updateSystemSelect();
updateCustomerList();
renderCustomerList();
updateNewCustomerButton();
updatePosList();
updateStorageInfo();
updateCompanyInfoDisplay();
updateServerStatus();
// Check for test data
if (systems.length === 0) {
if (confirm('Sistem verisi bulunamadı. Test verisi oluşturulsun mu?')) {
await addTestData();
}
}
// Update navbar logo
updateNavbarLogo();
} catch (error) {
console.error('Uygulama başlatma hatası:', error);
showError('Uygulama başlatılırken hata oluştu: ' + error.message);
}
});
// Update user information in navbar
function updateUserInfo() {
const userInfo = document.getElementById('userInfo');
if (userInfo && api.user) {
userInfo.textContent = `Hoş geldiniz, ${api.user.username}!`;
}
}
// Load initial data from server
async function loadInitialData() {
try {
// Load systems
systems = await api.getSystems();
// Load customers
const customers = await api.getCustomers();
customerData = {};
customers.forEach(customer => {
customerData[customer.name] = {
info: {
phone: customer.phone,
email: customer.email,
address: customer.address,
createdAt: customer.createdAt
},
pos: {}
};
});
// Load positions
positions = await api.getPositions();
// Load company data
companyData = await api.getCompany();
// Load PDF settings
try {
const settingsData = await api.getPDFSettings();
if (settingsData && settingsData.settings) {
pdfSettings = { ...pdfSettings, ...settingsData.settings };
}
} catch (settingsError) {
console.warn('PDF ayarları yüklenemedi, varsayılan ayarlar kullanılıyor:', settingsError);
}
console.log('Veri yükleme başarılı');
} catch (error) {
console.error('Veri yükleme hatası:', error);
showError('Veriler yüklenirken hata oluştu: ' + error.message);
// Hata durumunda varsayılan verilerle devam et
systems = [];
customerData = {};
positions = [];
}
}
// Logout function
async function logout() {
if (confirm('Çıkış yapmak istediğinizden emin misiniz?')) {
api.logout();
window.location.href = 'login.html';
}
}
// Bildirim fonksiyonları
function showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'toast error';
errorDiv.innerHTML = `
<div class="toast-content">
<span class="toast-icon">⚠️</span>
<span class="toast-message">${message}</span>
<button class="toast-close" onclick="this.parentElement.parentElement.remove()">×</button>
</div>
`;
document.body.appendChild(errorDiv);
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.remove();
}
}, 5000);
}
function showSuccess(message) {
const successDiv = document.createElement('div');
successDiv.className = 'toast success';
successDiv.innerHTML = `
<div class="toast-content">
<span class="toast-icon">✅</span>
<span class="toast-message">${message}</span>
<button class="toast-close" onclick="this.parentElement.parentElement.remove()">×</button>
</div>
`;
document.body.appendChild(successDiv);
setTimeout(() => {
if (successDiv.parentNode) {
successDiv.remove();
}
}, 3000);
}
// Update server status
async function updateServerStatus() {
const statusDiv = document.getElementById('serverStatus');
if (!statusDiv) return;
try {
const startTime = Date.now();
await api.getSystems();
const responseTime = Date.now() - startTime;
statusDiv.innerHTML = `
<div class="server-status-success">
<span class="status-icon">✅</span>
<div>
<strong>Sunucu Bağlantısı</strong><br>
<small>Çevrimiçi - Yanıt süresi: ${responseTime}ms</small>
</div>
</div>
`;
} catch (error) {
statusDiv.innerHTML = `
<div class="server-status-error">
<span class="status-icon">❌</span>
<div>
<strong>Sunucu Bağlantısı</strong><br>
<small>Çevrimdışı: ${error.message}</small>
</div>
</div>
`;
}
}
// Create backup
async function createBackup() {
const statusDiv = document.getElementById('backupStatus');
statusDiv.style.display = 'block';
statusDiv.className = 'status-message info';
statusDiv.innerHTML = '<div class="loading-spinner"></div> Yedek oluşturuluyor...';
try {
const backup = await api.createBackup();
// Create download link
const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `backup-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
statusDiv.className = 'status-message success';
statusDiv.innerHTML = '✅ Yedek başarı oluşturuldu ve indirildi.';
showSuccess('Yedekleme tamamlandı!');
} catch (error) {
statusDiv.className = 'status-message error';
statusDiv.innerHTML = `❌ Yedekleme hatası: ${error.message}`;
showError('Yedekleme başarısız: ' + error.message);
}
}
// Restore backup
async function restoreBackup(event) {
const file = event.target.files[0];
if (!file) return;
const statusDiv = document.getElementById('backupStatus');
statusDiv.style.display = 'block';
statusDiv.className = 'status-message info';
statusDiv.innerHTML = '<div class="loading-spinner"></div> Yedek geri yükleniyor...';
try {
const text = await file.text();
const backup = JSON.parse(text);
await api.restoreData(backup);
// Reload all data
await loadInitialData();
updateSystemSelect();
updateCustomerList();
renderCustomerList();
updatePosList();
updateCompanyInfoDisplay();
statusDiv.className = 'status-message success';
statusDiv.innerHTML = '✅ Yedek başarıyla geri yüklendi.';
showSuccess('Yedek geri yükleme tamamlandı!');
// Clear file input
event.target.value = '';
} catch (error) {
statusDiv.className = 'status-message error';
statusDiv.innerHTML = `❌ Geri yükleme hatası: ${error.message}`;
showError('Yedek geri yükleme başarısız: ' + error.message);
}
}
// API üzerinden kaydetme fonksiyonları
async function saveSystemData() {
try {
for (const system of systems) {
if (system._id) {
await api.updateSystem(system._id, system);
} else if (system.id) {
await api.updateSystem(system.id, system);
} else {
const newSystem = await api.createSystem(system);
system._id = newSystem._id || newSystem.id;
}
}
return true;
} catch (error) {
console.error('Sistem kaydetme hatası:', error);
showError('Sistem kaydedilemedi: ' + error.message);
return false;
}
}
async function saveCustomerData() {
try {
const customers = Object.entries(customerData).map(([name, data]) => ({
name,
phone: data.info?.phone || '',
email: data.info?.email || '',
address: data.info?.address || ''
}));
for (const customer of customers) {
if (customer._id) {
await api.updateCustomer(customer._id, customer);
} else {
const newCustomer = await api.createCustomer(customer);
customer._id = newCustomer._id || newCustomer.id;
}
}
return true;
} catch (error) {
console.error('Cari kaydetme hatası:', error);
showError('Cari kaydedilemedi: ' + error.message);
return false;
}
}
async function savePositionsData() {
try {
for (const position of positions) {
if (position._id) {
await api.updatePosition(position._id, position);
} else if (position.id) {
await api.updatePosition(position.id, position);
} else {
const newPosition = await api.createPosition(position);
position._id = newPosition._id || newPosition.id;
}
}
return true;
} catch (error) {
console.error('Poz kaydetme hatası:', error);
showError('Poz kaydedilemedi: ' + error.message);
return false;
}
}
// UI güncelleme fonksiyonları
async function updateSystemSelect() {
const select = document.getElementById('systemSelect');
if (select) {
select.innerHTML = '<option value="">Sistem seçin...</option>' +
systems.map(s => `<option value="${s._id || s.id}">${s.name}</option>`).join('');
}
}
async function updateCustomerList() {
const customerList = document.getElementById('customerList');
if (customerList) {
customerList.innerHTML = '';
Object.keys(customerData).forEach(customerName => {
const option = document.createElement('option');
option.value = customerName;
customerList.appendChild(option);
});
}
}
// Poz listesini güncelle
async function updatePosList() {
const container = document.getElementById('posListContainer');
const list = document.getElementById('posList');
if (!list) return;
if (positions.length === 0) {
if (container) container.style.display = 'none';
list.innerHTML = '<div class="empty-state">Henüz poz yok</div>';
return;
}
if (container) container.style.display = 'block';
let html = '';
positions.forEach((pos, index) => {
const camParts = pos.systemId?.parts?.filter(p => p.type === 'cam') ||
(pos.system && pos.system.parts ? pos.system.parts.filter(p => p.type === 'cam') : []);
let camInfoHtml = '';
if (camParts.length > 0) {
camParts.forEach(camPart => {
let camWidth = pos.width - 20;
let camHeight = pos.height - 20;
if (camPart.glassFormulas) {
try {
camWidth = evaluateFormula(camPart.glassFormulas.horizontal, pos.width, pos.height);
camHeight = evaluateFormula(camPart.glassFormulas.vertical, pos.width, pos.height);
} catch (error) {
console.error('Cam formülü hatası:', error);
}
}
camInfoHtml += `<div>${camPart.description || camPart.name}: ${Math.round(camWidth)}x${Math.round(camHeight)}mm (${(camPart.quantity || 1) * pos.quantity} adet)</div>`;
});
} else {
camInfoHtml = `<div>${pos.glassInfo?.width || pos.width - 20}x${pos.glassInfo?.height || pos.height - 20}mm (${pos.quantity} adet)</div>`;
}
const systemName = pos.systemId?.name || (pos.system ? pos.system.name : 'Bilinmeyen Sistem');
html += `
<div class="pos-item">
<div class="pos-header">
<div class="pos-title">Poz #${pos.pozNumber || (index + 1)} - ${pos.projectName || 'Projesiz'}</div>
<div class="pos-actions">
<button class="edit-btn" onclick="editPos(${index})">Düzenle</button>
<button class="pdf-btn" onclick="generatePosPDF(${index})">PDF</button>
<button class="delete-btn" onclick="deletePos(${index})">Sil</button>
</div>
</div>
<div class="pos-details">
<div class="pos-detail-item">
<div class="pos-detail-label">Sistem</div>
<div class="pos-detail-value">${systemName}</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Ölçüler</div>
<div class="pos-detail-value">${pos.width}x${pos.height}mm</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Adet</div>
<div class="pos-detail-value">${pos.quantity}</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Toplam Alan</div>
<div class="pos-detail-value">${pos.glassInfo?.totalArea || '0.00'} m²</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Cari</div>
<div class="pos-detail-value">${pos.customerName || 'Genel'}</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Tarih</div>
<div class="pos-detail-value">${new Date(pos.createdAt || Date.now()).toLocaleDateString('tr-TR')}</div>
</div>
</div>
<div class="pos-details">
<div class="pos-detail-item">
<div class="pos-detail-label">Yatay Profiller</div>
<div class="pos-detail-value">
${(pos.horizontalParts || []).map(p => `${p.name}: ${p.size}mm (${p.quantity} adet)`).join('<br>')}
</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Dikey Profiller</div>
<div class="pos-detail-value">
${(pos.verticalParts || []).map(p => `${p.name}: ${p.size}mm (${p.quantity} adet)`).join('<br>')}
</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Cam</div>
<div class="pos-detail-value">${camInfoHtml}</div>
</div>
</div>
</div>`;
});
list.innerHTML = html;
}
// Hesaplama fonksiyonu
async function calculateAndAddPos() {
const systemId = document.getElementById('systemSelect').value;
const width = parseInt(document.getElementById('width').value) || 0;
const height = parseInt(document.getElementById('height').value) || 0;
const quantity = parseInt(document.getElementById('quantity').value) || 1;
if (!systemId || !width || !height) {
showError('Lütfen sistem ve geçerli ölçüler seçin!');
return;
}
const system = systems.find(s => (s._id || s.id) === systemId);
if (!system) {
showError('Sistem bulunamadı!');
return;
}
try {
// Calculate parts
const horizontalParts = system.parts?.filter(p => p.type === 'yatay').map(p => {
let size = width - (p.reduction || 0);
if (p.advancedFormula?.formula) {
try {
size = evaluateFormula(p.advancedFormula.formula, width, height);
} catch (error) {
console.error('Formül hatası:', error);
}
}
return {
name: p.description || p.name,
size: Math.round(size),
quantity: (p.quantity || 1) * quantity,
formulaUsed: !!p.advancedFormula?.formula
};
}) || [];
const verticalParts = system.parts?.filter(p => p.type === 'dikey').map(p => {
let size = height - (p.reduction || 0);
if (p.advancedFormula?.formula) {
try {
size = evaluateFormula(p.advancedFormula.formula, width, height);
} catch (error) {
console.error('Formül hatası:', error);
}
}
return {
name: p.description || p.name,
size: Math.round(size),
quantity: (p.quantity || 1) * quantity,
formulaUsed: !!p.advancedFormula?.formula
};
}) || [];
const camParts = system.parts?.filter(p => p.type === 'cam').map(camPart => {
let glassWidth = width - 20;
let glassHeight = height - 20;
if (camPart.glassFormulas) {
try {
glassWidth = evaluateFormula(camPart.glassFormulas.horizontal, width, height);
glassHeight = evaluateFormula(camPart.glassFormulas.vertical, width, height);
} catch (error) {
console.error('Cam formülü hatası:', error);
}
}
const totalArea = (glassWidth * glassHeight * quantity / 1000000).toFixed(2);
return {
name: camPart.description || camPart.name,
quantity: (camPart.quantity || 1) * quantity,
size: `${Math.round(glassWidth)}x${Math.round(glassHeight)}`,
width: Math.round(glassWidth),
height: Math.round(glassHeight),
totalArea: totalArea
};
}) || [];
const totalArea = camParts.length > 0 ? camParts[0].totalArea : '0.00';
const newPosition = {
systemId: system._id || system.id,
projectName: document.getElementById('projectName').value,
width,
height,
quantity,
customerName: document.getElementById('customerName').value,
horizontalParts,
verticalParts,
glassParts: camParts,
glassInfo: {
width: camParts.length > 0 ? camParts[0].width : width - 20,
height: camParts.length > 0 ? camParts[0].height : height - 20,
totalArea
},
pozNumber: positions.length + 1
};
const savedPosition = await api.createPosition(newPosition);
positions.push(savedPosition);
updatePosList();
showSuccess('Hesaplama yapıldı ve poz listeye eklendi!');
// Clear form
document.getElementById('width').value = '';
document.getElementById('height').value = '';
document.getElementById('quantity').value = '1';
document.getElementById('projectName').value = '';
document.getElementById('customerName').value = '';
} catch (error) {
console.error('Poz hesaplama hatası:', error);
showError('Poz kaydedilemedi: ' + error.message);
}
}
// Override company functions
async function updateCompanyInfoDisplay() {
if (!companyData) return;
const elements = {
companyNameDisplay: companyData.name,
companyAddressDisplay: companyData.address || 'Adres belirtilmemiş',
companyContactDisplay: [
companyData.phone ? `📞 ${companyData.phone}` : '',
companyData.email ? `✉️ ${companyData.email}` : ''
].filter(Boolean).join(' | ') || 'İletişim bilgisi belirtilmemiş',
companyWebsiteDisplay: companyData.website ? `🌐 ${companyData.website}` : 'Website belirtilmemiş',
companyDescriptionDisplay: companyData.description || 'Açıklama belirtilmemiş'
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) element.textContent = value;
});
const logoDisplay = document.getElementById('companyLogoDisplay');
if (logoDisplay) {
logoDisplay.innerHTML = companyData.logo ? `<img src="${companyData.logo}" alt="Firma Logosu">` : '<div style="color: #a0aec0; font-size: 12px;">Logo yok</div>';
}
const navbarLogo = document.getElementById('navCompanyLogo');
if (navbarLogo) {
if (companyData.logo) {
navbarLogo.src = companyData.logo;
navbarLogo.style.display = 'block';
} else {
navbarLogo.style.display = 'none';
}
}
}
// Test verisi ekleme
async function addTestData() {
const testSystem = {
name: 'PVC Pencere Sistemi',
image: null,
parts: [
{
name: 'Üst Profil',
type: 'yatay',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin üst kısmında kullanılan yatay profil',
advancedFormula: {
formula: 'width - 10',
type: 'fixed'
}
},
{
name: 'Alt Profil',
type: 'yatay',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin alt kısmında kullanılan yatay profil',
advancedFormula: {
formula: 'width - 10',
type: 'fixed'
}
},
{
name: 'Sol Profil',
type: 'dikey',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin sol tarafında kullanılan dikey profil',
advancedFormula: {
formula: 'height - 10',
type: 'fixed'
}
},
{
name: 'Sağ Profil',
type: 'dikey',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin sağ tarafında kullanılan dikey profil',
advancedFormula: {
formula: 'height - 10',
type: 'fixed'
}
},
{
name: 'Cam',
type: 'cam',
quantity: 1,
reduction: 0,
image: null,
description: '4-16-4 İzolasyon Cam - Çift camlı izolasyon cam ünitesi',
glassFormulas: {
horizontal: 'width - 20',
horizontalType: 'fixed',
vertical: 'height - 20',
verticalType: 'fixed'
}
}
]
};
try {
const savedSystem = await api.createSystem(testSystem);
systems.push(savedSystem);
// Create test customer
const testCustomer = {
name: 'Test Cari',
phone: '0555 555 55 55',
email: 'test@cari.com',
address: 'Test Adresi'
};
const savedCustomer = await api.createCustomer(testCustomer);
customerData[testCustomer.name] = {
info: {
phone: savedCustomer.phone,
email: savedCustomer.email,
address: savedCustomer.address,
createdAt: savedCustomer.createdAt
},
pos: {}
};
updateSystemSelect();
updateCustomerList();
renderCustomerList();
updateNewCustomerButton();
showSuccess('Test verileri başarıyla oluşturuldu!');
} catch (error) {
console.error('Test verisi oluşturma hatası:', error);
showError('Test verileri oluşturulamadı: ' + error.message);
}
}
// Poz silme
async function deletePos(index) {
if (!confirm('Bu poz silinecek. Emin misiniz?')) return;
const position = positions[index];
if (!position) return;
try {
const positionId = position._id || position.id;
if (positionId) {
await api.deletePosition(positionId);
}
positions.splice(index, 1);
updatePosList();
showSuccess('Poz başarıyla silindi!');
} catch (error) {
console.error('Poz silme hatası:', error);
showError('Poz silinemedi: ' + error.message);
}
}
// GÜNCELLENMİŞ PDF OLUŞTURMA FONKSİYONU - MM ve ADET sütunları yer değiştirdi + Firma bilgileri eklendi
const generateCalculationPDF = (calc) => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
setupPDFFont(doc);
// PDF ayarlarını kullanarak firma bilgilerini ekle
let yPos = addCompanyHeaderToPDF(doc, 1, 1);
// Sayfa başlığı - PDF ayarlarından yazı boyutu kullan
doc.setFontSize(pdfSettings.headerFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars('PENCERE ÖLÇÜ HESAPLAMA RAPORU'), 105, yPos, { align: 'center' });
yPos += pdfSettings.tableRowHeight * 2;
// SOL TARAF: Pencere resmi ve ölçüler
const leftStartX = pdfSettings.tableMargin;
// Poz numarası ve Adet bilgisi - AYNI HİZADA - PDF ayarlarından yazı boyutu kullan
doc.setFontSize(pdfSettings.subheaderFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars(`Poz #${calc.pozNumber || '1'}`), leftStartX, yPos);
// Adet bilgisi - Poz numarasıyla aynı hizada
doc.text(`Adet: ${calc.quantity}`, leftStartX + 40, yPos);
// Pencere resmi (PDF ayarlarından boyut kullan)
if (calc.system.image) {
try {
const img = new Image();
img.src = calc.system.image;
// PDF ayarlarından resim boyutu
const imgWidth = pdfSettings.windowImageWidth; // Ayarlardan pencere resmi boyutu
const imgHeight = pdfSettings.windowImageHeight;
const imgX = leftStartX;
const imgY = yPos + pdfSettings.tableRowHeight;
// Pencere resmi
doc.addImage(img, 'JPEG', imgX, imgY, imgWidth, imgHeight);
// YATAY ÖLÇÜ ÇİZGİSİ - resmin altında
const horizontalLineY = imgY + imgHeight + pdfSettings.tableRowHeight;
doc.setDrawColor(0, 0, 0);
doc.setLineWidth(pdfSettings.separatorThickness * 0.1); // Çizgi kalınlığını ayarla
doc.line(imgX, horizontalLineY, imgX + imgWidth, horizontalLineY);
// Yatay ölçü ok işaretleri
doc.line(imgX, horizontalLineY - 2, imgX, horizontalLineY + 2);
doc.line(imgX + imgWidth, horizontalLineY - 2, imgX + imgWidth, horizontalLineY + 2);
// Yatay ölçü değeri - PDF ayarlarından yazı boyutu kullan
doc.setFontSize(pdfSettings.smallFontSize);
doc.text(`${calc.width} mm`, imgX + (imgWidth/2) - 10, horizontalLineY + 7);
// DİKEY ÖLÇÜ ÇİZGİSİ - resmin sağında
const verticalLineX = imgX + imgWidth + 5;
const verticalLineY1 = imgY;
const verticalLineY2 = imgY + imgHeight;
// Dikey ölçü çizgisi
doc.line(verticalLineX, verticalLineY1, verticalLineX, verticalLineY2);
// Dikey ölçü ok işaretleri
doc.line(verticalLineX - 2, verticalLineY1, verticalLineX + 2, verticalLineY1);
doc.line(verticalLineX - 2, verticalLineY2, verticalLineX + 2, verticalLineY2);
// Dikey ölçü değeri - dikey yazı - PDF ayarlarından yazı boyutu kullan
doc.text(`${calc.height} mm`, verticalLineX + 3, imgY + (imgHeight/2), { angle: 90 });
} catch (error) {
console.error('Resim PDF\'e eklenemedi:', error);
}
} else {
// Resim yoksa sadece ölçüleri göster - PDF ayarlarından yazı boyutu kullan
doc.setFontSize(pdfSettings.subheaderFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(`Ölçüler: ${calc.width}x${calc.height}mm`, leftStartX, yPos + 20);
doc.text(`Adet: ${calc.quantity}`, leftStartX, yPos + 30);
}
// SAĞ TARAF: Proje bilgileri
const rightStartX = pdfSettings.tableMargin + 90;
const tableY = yPos;
// Bilgi tablosu başlığı - PDF ayarlarından yazı boyutu kullan
doc.setFontSize(pdfSettings.subheaderFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars('PROJE BİLGİLERİ'), rightStartX, tableY);
// Tablo içeriği - PDF ayarlarından yazı boyutu kullan
doc.setFontSize(pdfSettings.normalFontSize);
doc.setFont(undefined, 'normal');
const infoData = [
{ label: 'Sistem', value: calc.system.name },
{ label: 'Proje', value: calc.projectName || 'Belirtilmemiş' },
{ label: 'Cari', value: calc.customerName || 'Belirtilmemiş' },
{ label: 'Tarih', value: new Date(calc.timestamp).toLocaleDateString('tr-TR') },
{ label: 'Toplam Alan', value: `${calc.glassInfo.totalArea} m²` }
];
let infoY = tableY + pdfSettings.tableRowHeight;
infoData.forEach(item => {
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars(item.label + ':'), rightStartX, infoY);
doc.setFont(undefined, 'normal');
// Uzun değerleri kısalt
let value = item.value;
if (value.length > 25) {
value = value.substring(0, 25) + '...';
}
doc.text(fixTurkishChars(value), rightStartX + 22, infoY);
infoY += pdfSettings.tableRowHeight;
});
// PROFİL LİSTESİ - Bilgi tablosundan sonra - PDF ayarlarından mesafe kullan
yPos = Math.max(infoY + pdfSettings.tableRowHeight * 2, yPos + 80);
doc.setFontSize(pdfSettings.subheaderFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars('PROFİL LİSTESİ'), pdfSettings.tableMargin, yPos);
yPos += pdfSettings.tableRowHeight;
// Tablo başlıkları - PDF ayarlarından sütun genişlikleri kullan
doc.setFontSize(pdfSettings.normalFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
const pageWidth = 210; // A4 genişlik
const descWidth = (pageWidth - 2 * pdfSettings.tableMargin) * pdfSettings.descriptionColumnWidth / 100;
const mmWidth = (pageWidth - 2 * pdfSettings.tableMargin) * pdfSettings.mmColumnWidth / 100;
const qtyWidth = (pageWidth - 2 * pdfSettings.tableMargin) * pdfSettings.quantityColumnWidth / 100;
const descX = pdfSettings.tableMargin;
const mmX = descX + descWidth;
const qtyX = mmX + mmWidth;
doc.text(fixTurkishChars('AÇIKLAMA'), descX, yPos);
doc.text(fixTurkishChars('MM'), mmX + 5, yPos); // MM sütunu ADET'ten önce
doc.text(fixTurkishChars('ADET'), qtyX + 5, yPos); // ADET sütunu MM'den sonra
yPos += pdfSettings.tableRowHeight;
// Tablo kenar çizgileri - PDF ayarlarından
if (pdfSettings.showTableBorders) {
doc.line(descX, yPos, qtyX + qtyWidth, yPos);
}
yPos += 2;
// Yatay profiller
if (calc.horizontalParts.length > 0) {
doc.setFontSize(pdfSettings.smallFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars('YATAY PROFİLLER'), descX, yPos);
yPos += pdfSettings.tableRowHeight;
calc.horizontalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, 2, 1);
}
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars(description), descX, yPos);
doc.text(part.size.toString(), mmX + 5, yPos); // MM değeri
doc.text(part.quantity.toString(), qtyX + 5, yPos); // ADET değeri
yPos += pdfSettings.tableRowHeight;
});
yPos += 2;
}
// Dikey profiller
if (calc.verticalParts.length > 0) {
doc.setFontSize(pdfSettings.smallFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars('DİKEY PROFİLLER'), descX, yPos);
yPos += pdfSettings.tableRowHeight;
calc.verticalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, 2, 1);
}
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars(description), descX, yPos);
doc.text(part.size.toString(), mmX + 5, yPos); // MM değeri
doc.text(part.quantity.toString(), qtyX + 5, yPos); // ADET değeri
yPos += pdfSettings.tableRowHeight;
});
yPos += 2;
}
// TÜM CAMLARI GÖSTER - PDF ayarlarından sütun genişlikleri kullan
const camParts = calc.system.parts.filter(p => p.type === 'cam');
if (camParts.length > 0) {
doc.setFontSize(pdfSettings.smallFontSize);
doc.setFont(undefined, pdfSettings.fontWeight);
doc.text(fixTurkishChars('CAM BİLGİSİ'), descX, yPos);
yPos += pdfSettings.tableRowHeight;
camParts.forEach(camPart => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, 2, 1);
}
// Cam açıklaması
const camDescription = camPart.description || 'Cam';
doc.setFont(undefined, 'normal');
const camDescShort = camDescription.length > 35 ? camDescription.substring(0, 35) + '...' : camDescription;
doc.text(fixTurkishChars(camDescShort), descX, yPos);
// HER CAM İÇİN DOĞRU ÖLÇÜLERİ HESAPLA
let camWidth = calc.width - 20;
let camHeight = calc.height - 20;
// Eğer camın kendi formülü varsa onu kullan
if (camPart.glassFormulas) {
try {
camWidth = evaluateFormula(camPart.glassFormulas.horizontal, calc.width, calc.height);
camHeight = evaluateFormula(camPart.glassFormulas.vertical, calc.width, calc.height);
} catch (error) {
console.error('Cam formülü hesaplanırken hata:', error);
}
}
doc.text(`${Math.round(camWidth)}x${Math.round(camHeight)}`, mmX + 5, yPos); // MM değeri
doc.text((camPart.quantity * calc.quantity).toString(), qtyX + 5, yPos); // ADET değeri
yPos += pdfSettings.tableRowHeight;
});
}
doc.save(fixTurkishChars(`pencere-${calc.projectName || 'rapor'}-${new Date().getTime()}.pdf`));
};
// Tüm pozları PDF olarak oluştur - MM ve ADET sütunları yer değiştirdi + Firma bilgileri eklendi
function generateAllPosPDF() {
if (posList.length === 0) {
alert('PDF oluşturmak için en az bir poz olmalıdır!');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
setupPDFFont(doc);
let currentPage = 1;
let totalPages = 1;
posList.forEach((pos, index) => {
if (index > 0) {
doc.addPage();
currentPage++;
}
// Firma bilgilerini her sayfaya ekle
let yPos = addCompanyHeaderToPDF(doc, currentPage, totalPages);
// Sayfa başlığı
doc.setFontSize(16);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('PENCERE ÖLÇÜ HESAPLAMA RAPORU'), 105, yPos, { align: 'center' });
yPos += 15;
// SOL TARAF: Pencere resmi ve ölçüler
const leftStartX = 20;
// Poz numarası ve Adet bilgisi - AYNI HİZADA
doc.setFontSize(12);
doc.setFont(undefined, 'bold');
doc.text(`Poz #${index + 1}`, leftStartX, yPos);
// Adet bilgisi - Poz numarasıyla aynı hizada
doc.text(`Adet: ${pos.quantity}`, leftStartX + 40, yPos);
// Pencere resmi (küçültülmüş boyut)
if (pos.system.image) {
try {
const img = new Image();
img.src = pos.system.image;
// PDF ayarlarından pencere resmi boyutu - bağımsız
const imgWidth = pdfSettings.windowImageWidth;
const imgHeight = pdfSettings.windowImageHeight;
const imgX = leftStartX; // SOL TARAFTA
const imgY = yPos + 5; // Poz numarasından sonra
// Pencere resmi
doc.addImage(img, 'JPEG', imgX, imgY, imgWidth, imgHeight);
// YATAY ÖLÇÜ ÇİZGİSİ - resmin altında - PDF ayarlarından çizgi kalınlığı
const horizontalLineY = imgY + imgHeight + 5;
doc.setDrawColor(0, 0, 0);
doc.setLineWidth(pdfSettings.dimensionLineThickness);
doc.line(imgX, horizontalLineY, imgX + imgWidth, horizontalLineY);
// Yatay ölçü ok işaretleri - PDF ayarlarından yazı boyutu
doc.line(imgX, horizontalLineY - 2, imgX, horizontalLineY + 2);
doc.line(imgX + imgWidth, horizontalLineY - 2, imgX + imgWidth, horizontalLineY + 2);
// Yatay ölçü değeri - PDF ayarlarından yazı boyutu
doc.setFontSize(pdfSettings.dimensionTextSize);
doc.text(`${pos.width} mm`, imgX + (imgWidth/2) - 10, horizontalLineY + 7);
// DİKEY ÖLÇÜ ÇİZGİSİ - resmin sağında
const verticalLineX = imgX + imgWidth + 5;
const verticalLineY1 = imgY;
const verticalLineY2 = imgY + imgHeight;
// Dikey ölçü çizgisi
doc.line(verticalLineX, verticalLineY1, verticalLineX, verticalLineY2);
// Dikey ölçü ok işaretleri
doc.line(verticalLineX - 2, verticalLineY1, verticalLineX + 2, verticalLineY1);
doc.line(verticalLineX - 2, verticalLineY2, verticalLineX + 2, verticalLineY2);
// Dikey ölçü değeri - dikey yazı - PDF ayarlarından yazı boyutu
doc.setFontSize(pdfSettings.dimensionTextSize);
doc.text(`${pos.height} mm`, verticalLineX + 3, imgY + (imgHeight/2), { angle: 90 });
} catch (error) {
console.error('Resim PDF\'e eklenemedi:', error);
}
} else {
// Resim yoksa sadece ölçüleri göster
doc.setFontSize(11);
doc.setFont(undefined, 'bold');
doc.text(`Ölçüler: ${pos.width}x${pos.height}mm`, leftStartX, yPos + 20);
doc.text(`Adet: ${pos.quantity}`, leftStartX, yPos + 30);
}
// SAĞ TARAF: Proje bilgileri
const rightStartX = 110;
const tableY = yPos;
// Bilgi tablosu başlığı
doc.setFontSize(12);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('PROJE BİLGİLERİ'), rightStartX, tableY);
// Tablo içeriği
doc.setFontSize(9);
doc.setFont(undefined, 'normal');
const infoData = [
{ label: 'Sistem', value: pos.system.name },
{ label: 'Proje', value: pos.projectName || 'Belirtilmemiş' },
{ label: 'Cari', value: pos.customerName || 'Genel' },
{ label: 'Tarih', value: new Date(pos.timestamp).toLocaleDateString('tr-TR') },
{ label: 'Toplam Alan', value: `${pos.glassInfo.totalArea} m²` }
];
let infoY = tableY + 8;
infoData.forEach(item => {
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars(item.label + ':'), rightStartX, infoY);
doc.setFont(undefined, 'normal');
let value = item.value;
if (value.length > 25) {
value = value.substring(0, 25) + '...';
}
doc.text(fixTurkishChars(value), rightStartX + 22, infoY);
infoY += 5;
});
// PROFİL LİSTESİ - Bilgi tablosundan sonra
yPos = Math.max(infoY + 10, yPos + 80); // Resim yüksekliğine göre ayarla
doc.setFontSize(12);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('PROFİL LİSTESİ'), 20, yPos);
yPos += 8;
// Tablo başlıkları - MM ve ADET sütunları yer değiştirdi
doc.setFontSize(8);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('AÇIKLAMA'), 20, yPos);
doc.text(fixTurkishChars('MM'), 120, yPos); // MM sütunu ADET'ten önce
doc.text(fixTurkishChars('ADET'), 160, yPos); // ADET sütunu MM'den sonra
yPos += 4;
doc.line(20, yPos, 190, yPos);
yPos += 3;
// Yatay profiller
if (pos.horizontalParts.length > 0) {
doc.setFontSize(7);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('YATAY PROFİLLER'), 20, yPos);
yPos += 4;
pos.horizontalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, ++currentPage, totalPages);
}
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars(description), 20, yPos);
doc.text(part.size.toString(), 120, yPos); // MM değeri
doc.text(part.quantity.toString(), 160, yPos); // ADET değeri
yPos += 4;
});
yPos += 2;
}
// Dikey profiller
if (pos.verticalParts.length > 0) {
doc.setFontSize(7);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('DİKEY PROFİLLER'), 20, yPos);
yPos += 4;
pos.verticalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, ++currentPage, totalPages);
}
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars(description), 20, yPos);
doc.text(part.size.toString(), 120, yPos); // MM değeri
doc.text(part.quantity.toString(), 160, yPos); // ADET değeri
yPos += 4;
});
yPos += 2;
}
// TÜM CAMLARI GÖSTER - MM ve ADET sütunları yer değiştirdi
const camParts = pos.system.parts.filter(p => p.type === 'cam');
if (camParts.length > 0) {
doc.setFontSize(7);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('CAM BİLGİSİ'), 20, yPos);
yPos += 4;
camParts.forEach(camPart => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, ++currentPage, totalPages);
}
// Cam açıklaması
const camDescription = camPart.description || 'Cam';
doc.setFont(undefined, 'normal');
const camDescShort = camDescription.length > 35 ? camDescription.substring(0, 35) + '...' : camDescription;
doc.text(fixTurkishChars(camDescShort), 20, yPos);
// HER CAM İÇİN DOĞRU ÖLÇÜLERİ HESAPLA
let camWidth = pos.width - 20;
let camHeight = pos.height - 20;
// Eğer camın kendi formülü varsa onu kullan
if (camPart.glassFormulas) {
try {
camWidth = evaluateFormula(camPart.glassFormulas.horizontal, pos.width, pos.height);
camHeight = evaluateFormula(camPart.glassFormulas.vertical, pos.width, pos.height);
} catch (error) {
console.error('Cam formülü hesaplanırken hata:', error);
// Hata durumunda varsayılan değerleri kullan
}
}
doc.text(`${Math.round(camWidth)}x${Math.round(camHeight)}`, 120, yPos); // MM değeri
doc.text((camPart.quantity * pos.quantity).toString(), 160, yPos); // ADET değeri
yPos += 4;
});
}
});
doc.save(fixTurkishChars('tum-pencere-pozlari.pdf'));
}
// GÜNCELLENMİŞ HESAPLAMA FONKSİYONU - Profil adetleri düzeltildi
function calculateAndAddPos() {
const system = systems.find(s => s.id === currentSystemId);
const width = parseInt($('width').value) || 0;
const height = parseInt($('height').value) || 0;
const quantity = parseInt($('quantity').value) || 1;
if (!system || !width || !height) {
alert('Lütfen sistem ve geçerli ölçüler seçin!');
return;
}
// Gelişmiş profil hesaplama
const horizontalParts = system.parts.filter(p => p.type === 'yatay').map(p => {
let size = width - p.reduction;
// Gelişmiş formül varsa kullan
if (p.advancedFormula) {
try {
size = evaluateFormula(p.advancedFormula.formula, width, height);
} catch (error) {
console.error('Formül hatası:', error);
// Hata durumunda basit düşüm kullan
size = width - p.reduction;
}
}
return {
name: p.description,
size: Math.round(size),
quantity: p.quantity * quantity, // DÜZELTME: Profil adedi pencere adedi ile çarpılıyor
formulaUsed: p.advancedFormula ? true : false
};
});
const verticalParts = system.parts.filter(p => p.type === 'dikey').map(p => {
let size = height - p.reduction;
// Gelişmiş formül varsa kullan
if (p.advancedFormula) {
try {
size = evaluateFormula(p.advancedFormula.formula, width, height);
} catch (error) {
console.error('Formül hatası:', error);
// Hata durumunda basit düşüm kullan
size = height - p.reduction;
}
}
return {
name: p.description,
size: Math.round(size),
quantity: p.quantity * quantity, // DÜZELTME: Profil adedi pencere adedi ile çarpılıyor
formulaUsed: p.advancedFormula ? true : false
};
});
// HER CAM İÇİN AYRI AYRI HESAPLAMA YAP
const glassParts = system.parts.filter(p => p.type === 'cam').map(camPart => {
let glassWidth = width - 20;
let glassHeight = height - 20;
// Eğer camın kendi formülü varsa onu kullan
if (camPart.glassFormulas) {
try {
glassWidth = evaluateFormula(camPart.glassFormulas.horizontal, width, height);
glassHeight = evaluateFormula(camPart.glassFormulas.vertical, width, height);
} catch (error) {
console.error('Cam formülü hesaplanırken hata:', error);
// Hata durumunda varsayılan değerleri kullan
}
}
const totalArea = (glassWidth * glassHeight * quantity / 1000000).toFixed(2);
return {
name: camPart.description,
quantity: camPart.quantity * quantity, // DÜZELTME: Cam adedi pencere adedi ile çarpılıyor
size: `${Math.round(glassWidth)}x${Math.round(glassHeight)}`,
width: Math.round(glassWidth),
height: Math.round(glassHeight),
totalArea: totalArea
};
});
// Toplam alanı hesapla (ilk camın alanını kullan)
const totalArea = glassParts.length > 0 ? glassParts[0].totalArea : '0.00';
const newPos = {
system,
width,
height,
quantity: quantity,
projectName: $('projectName').value,
customerName: $('customerName').value,
horizontalParts,
verticalParts,
glassParts: glassParts,
glassInfo: {
width: glassParts.length > 0 ? glassParts[0].width : width - 20,
height: glassParts.length > 0 ? glassParts[0].height : height - 20,
totalArea
},
timestamp: new Date().toISOString(),
id: Date.now().toString(),
pozNumber: posList.length + 1
};
posList.push(newPos);
if (savePosList()) {
updatePosList();
alert('Hesaplama yapıldı ve poz listeye eklendi!');
}
}
// Kalan fonksiyonlar aynı kalacak...
// (Depolama, veri yönetimi, modal işlemleri vb. fonksiyonlar değişmedi)
// Depolama bilgilerini güncelle
function updateStorageInfo() {
const posCount = $('posCount');
const storageSize = $('storageSize');
if (posCount) {
posCount.textContent = posList.length;
}
if (storageSize) {
const size = calculateStorageSize();
storageSize.textContent = size;
}
}
function updateStorageStatus() {
const statusElement = $('storageStatus');
if (!statusElement) return;
const totalSize = calculateTotalStorageSize();
const maxSize = 5 * 1024 * 1024; // 5MB
const usagePercent = (totalSize / maxSize) * 100;
let statusHTML = `
<div style="margin-bottom: 10px;">
<strong>Toplam Kullanım:</strong> ${(totalSize / 1024).toFixed(2)} KB / ${(maxSize / 1024).toFixed(2)} KB
</div>
<div style="background: #e2e8f0; border-radius: 10px; height: 20px; margin-bottom: 10px;">
<div style="background: ${usagePercent > 80 ? '#e53e3e' : usagePercent > 60 ? '#d69e2e' : '#38a169'};
width: ${Math.min(usagePercent, 100)}%; height: 100%; border-radius: 10px; transition: width 0.3s;"></div>
</div>
<div style="font-size: 14px; color: #718096;">
Sistemler: ${systems.length} | Cariler: ${Object.keys(customerData).length} | Pozlar: ${posList.length}
</div>
`;
statusElement.innerHTML = statusHTML;
if (usagePercent > 80) {
showStorageWarning();
}
}
function calculateStorageSize() {
const dataStr = JSON.stringify(posList);
const sizeInBytes = new Blob([dataStr]).size;
return (sizeInBytes / 1024).toFixed(2) + ' KB';
}
function calculateTotalStorageSize() {
const systemsSize = new Blob([JSON.stringify(systems)]).size;
const customersSize = new Blob([JSON.stringify(customerData)]).size;
const companySize = new Blob([JSON.stringify(companyData)]).size;
const posSize = new Blob([JSON.stringify(posList)]).size;
return systemsSize + customersSize + companySize + posSize;
}
function showStorageWarning() {
const warning = $('storageWarning');
warning.style.display = 'block';
setTimeout(() => {
warning.style.display = 'none';
}, 5000);
}
// Geliştirilmiş save fonksiyonları
function savePosList() {
try {
localStorage.setItem('posList', JSON.stringify(posList));
updateStorageInfo();
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('Depolama alanı doldu! Lütfen eski pozları temizleyin veya PDF olarak dışa aktarın.');
return false;
}
throw e;
}
}
function saveData() {
try {
localStorage.setItem('pencereSystems', JSON.stringify(systems));
updateStorageStatus();
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('Depolama alanı doldu! Lütfen eski sistemleri silin veya resim boyutlarını küçültün.');
return false;
}
throw e;
}
}
function saveCustomerData() {
try {
localStorage.setItem('customerData', JSON.stringify(customerData));
updateStorageStatus();
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('Depolama alanı doldu! Lütfen eski carileri silin.');
return false;
}
throw e;
}
}
// Veri temizleme fonksiyonları
function clearPosList() {
if (confirm('Tüm hesaplanan pozlar silinecek. Emin misiniz?')) {
posList = [];
if (savePosList()) {
updatePosList();
alert('Poz listesi temizlendi!');
}
}
}
function clearAllData() {
if (confirm('TÜM veriler (sistemler, cariler, pozlar) silinecek. Bu işlem geri alınamaz! Emin misiniz?')) {
localStorage.clear();
systems = [];
customerData = {};
posList = [];
updateSystemSelect();
updateCustomerList();
renderCustomerList();
updatePosList();
updateStorageInfo();
updateStorageStatus();
alert('Tüm veriler temizlendi!');
}
}
// Veri dışa/içe aktarma
function exportAllData() {
const allData = {
systems: systems,
customerData: customerData,
posList: posList,
exportDate: new Date().toISOString(),
version: '1.0'
};
const dataStr = JSON.stringify(allData, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `pencere-hesaplama-backup-${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
}
function importData() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (event) => {
try {
const importedData = JSON.parse(event.target.result);
if (confirm('Mevcut verilerin üzerine yazılacak. Emin misiniz?')) {
systems = importedData.systems || [];
customerData = importedData.customerData || {};
posList = importedData.posList || [];
saveData();
saveCustomerData();
savePosList();
updateSystemSelect();
updateCustomerList();
renderCustomerList();
updatePosList();
updateStorageInfo();
updateStorageStatus();
alert('Veriler başarıyla içe aktarıldı!');
}
} catch (error) {
alert('Dosya okunamadı: ' + error.message);
}
};
reader.readAsText(file);
};
input.click();
}
// Yeni Cari butonunu güncelleme fonksiyonu
function updateNewCustomerButton() {
const container = $('newCustomerButtonContainer');
const hasCustomers = Object.keys(customerData).length > 0;
if (!hasCustomers) {
container.innerHTML = '<button class="add-btn" onclick="showPage(\'customerPage\')">+ Yeni Cari</button>';
} else {
container.innerHTML = '';
}
}
// Yeni Cari Ekleme Modal Fonksiyonları
function openAddCustomerModal() {
$('addCustomerModal').style.display = 'block';
// Form alanlarını temizle
$('modalNewCustomerName').value = '';
$('modalNewCustomerPhone').value = '';
$('modalNewCustomerEmail').value = '';
$('modalNewCustomerAddress').value = '';
}
function closeAddCustomerModal() {
$('addCustomerModal').style.display = 'none';
}
// Poz listesini güncelle
function updatePosList() {
const container = $('posListContainer');
const list = $('posList');
if (posList.length === 0) {
container.style.display = 'none';
list.innerHTML = '<div class="empty-state">Henüz poz yok</div>';
return;
}
container.style.display = 'block';
let html = '';
posList.forEach((pos, index) => {
// Tüm camları al
const camParts = pos.system.parts.filter(p => p.type === 'cam');
let camInfoHtml = '';
if (camParts.length > 0) {
camParts.forEach(camPart => {
// Her cam için doğru ölçüleri göster
let camWidth = pos.width - 20;
let camHeight = pos.height - 20;
if (camPart.glassFormulas) {
try {
camWidth = evaluateFormula(camPart.glassFormulas.horizontal, pos.width, pos.height);
camHeight = evaluateFormula(camPart.glassFormulas.vertical, pos.width, pos.height);
} catch (error) {
console.error('Cam formülü hesaplanırken hata:', error);
}
}
camInfoHtml += `<div>${camPart.description}: ${Math.round(camWidth)}x${Math.round(camHeight)}mm (${camPart.quantity * pos.quantity} adet)</div>`;
});
} else {
camInfoHtml = `<div>${pos.glassInfo.width}x${pos.glassInfo.height}mm (${pos.quantity} adet)</div>`;
}
html += `
<div class="pos-item">
<div class="pos-header">
<div class="pos-title">Poz #${index + 1} - ${pos.projectName || 'Projesiz'}</div>
<div class="pos-actions">
<button class="edit-btn" onclick="editPos(${index})">Düzenle</button>
<button class="pdf-btn" onclick="generatePosPDF(${index})">PDF</button>
<button class="delete-btn" onclick="deletePos(${index})">Sil</button>
</div>
</div>
<div class="pos-details">
<div class="pos-detail-item">
<div class="pos-detail-label">Sistem</div>
<div class="pos-detail-value">${pos.system.name}</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Ölçüler</div>
<div class="pos-detail-value">${pos.width}x${pos.height}mm</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Adet</div>
<div class="pos-detail-value">${pos.quantity}</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Toplam Alan</div>
<div class="pos-detail-value">${pos.glassInfo.totalArea} m²</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Cari</div>
<div class="pos-detail-value">${pos.customerName || 'Genel'}</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Tarih</div>
<div class="pos-detail-value">${new Date(pos.timestamp).toLocaleDateString('tr-TR')}</div>
</div>
</div>
<div class="pos-details">
<div class="pos-detail-item">
<div class="pos-detail-label">Yatay Profiller</div>
<div class="pos-detail-value">
${pos.horizontalParts.map(p => `${p.name}: ${p.size}mm (${p.quantity} adet)`).join('<br>')}
</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Dikey Profiller</div>
<div class="pos-detail-value">
${pos.verticalParts.map(p => `${p.name}: ${p.size}mm (${p.quantity} adet)`).join('<br>')}
</div>
</div>
<div class="pos-detail-item">
<div class="pos-detail-label">Cam</div>
<div class="pos-detail-value">${camInfoHtml}</div>
</div>
</div>
</div>`;
});
list.innerHTML = html;
}
// Poz düzenleme modalını aç (Ana Sayfa)
function editPos(index) {
const pos = posList[index];
editingPosIndex = index;
editingCustomerPos = { customerName: null, pozNumber: null };
$('editWidth').value = pos.width;
$('editHeight').value = pos.height;
$('editQuantity').value = pos.quantity;
$('editProjectName').value = pos.projectName || '';
$('editCustomerName').value = pos.customerName || '';
$('editPosModal').style.display = 'block';
}
// Cari poz düzenleme modalını aç
function editCustomerPos(customerName, pozNumber) {
const customer = customerData[customerName];
const pos = customer.pos[pozNumber];
editingPosIndex = null;
editingCustomerPos = { customerName, pozNumber };
$('editWidth').value = pos.width;
$('editHeight').value = pos.height;
$('editQuantity').value = pos.quantity;
$('editProjectName').value = pos.projectName || '';
$('editCustomerName').value = pos.customerName || '';
$('editPosModal').style.display = 'block';
}
// Poz düzenleme modalını kapat
function closeEditPosModal() {
$('editPosModal').style.display = 'none';
editingPosIndex = null;
editingCustomerPos = { customerName: null, pozNumber: null };
}
// Poz güncelleme
function updatePos() {
const width = parseInt($('editWidth').value) || 0;
const height = parseInt($('editHeight').value) || 0;
const quantity = parseInt($('editQuantity').value) || 1;
const projectName = $('editProjectName').value;
const customerName = $('editCustomerName').value;
if (width <= 0 || height <= 0) {
alert('Lütfen geçerli ölçüler girin!');
return;
}
if (editingPosIndex !== null) {
const pos = posList[editingPosIndex];
posList[editingPosIndex] = {
...pos,
width,
height,
quantity,
projectName,
customerName,
timestamp: new Date().toISOString()
};
if (savePosList()) {
updatePosList();
closeEditPosModal();
alert('Poz başarıyla güncellendi!');
}
} else if (editingCustomerPos.customerName && editingCustomerPos.pozNumber) {
const customer = customerData[editingCustomerPos.customerName];
const pos = customer.pos[editingCustomerPos.pozNumber];
customer.pos[editingCustomerPos.pozNumber] = {
...pos,
width,
height,
quantity,
projectName,
customerName,
timestamp: new Date().toISOString()
};
if (saveCustomerData()) {
loadCustomerData();
closeEditPosModal();
alert('Cari poz başarıyla güncellendi!');
}
}
}
// Poz silme
function deletePos(index) {
if (!confirm('Bu poz silinecek. Emin misiniz?')) return;
posList.splice(index, 1);
if (savePosList()) {
updatePosList();
}
}
// Poz PDF oluşturma
function generatePosPDF(index) {
const pos = posList[index];
generateCalculationPDF(pos);
}
// Tüm pozları carilere kaydet
function saveAllPos() {
if (posList.length === 0) {
alert('Kaydedilecek poz bulunamadı!');
return;
}
posList.forEach(pos => {
if (!pos.customerName) {
pos.customerName = "Genel";
}
if (!customerData[pos.customerName]) {
customerData[pos.customerName] = {
info: {
phone: '',
email: '',
address: '',
createdAt: new Date().toISOString()
},
pos: {}
};
}
const customer = customerData[pos.customerName];
const pozNumber = Object.keys(customer.pos).length + 1;
customer.pos[pozNumber] = {
...pos,
pozNumber: pozNumber
};
});
if (saveCustomerData()) {
updateCustomerList();
updateNewCustomerButton();
posList = [];
savePosList();
updatePosList();
showSaveNotification();
}
}
// Kaydetme bildirimi göster
function showSaveNotification() {
const notification = $('saveNotification');
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 3000);
}
// Bölüm aç/kapa fonksiyonu
function toggleSection(sectionId) {
const section = $(sectionId);
section.classList.toggle('show');
}
// Formül bölümlerini aç/kapa
function toggleFormulaSections() {
const partType = $('modalPartType').value;
const camFormulaSection = $('camFormulaSection');
const advancedFormulaSection = $('advancedFormulaSection');
const reductionField = $('reductionField');
const button = $('modalProfileButton');
if (partType === 'cam') {
camFormulaSection.style.display = 'block';
advancedFormulaSection.style.display = 'none';
reductionField.style.display = 'none';
button.textContent = 'Cam Ekle';
$('formulaPreview').classList.remove('show');
$('formulaTest').classList.remove('show');
$('formulaTestResult').classList.remove('show');
} else if (partType === 'yatay' || partType === 'dikey') {
camFormulaSection.style.display = 'none';
advancedFormulaSection.style.display = 'block';
reductionField.style.display = 'block';
button.textContent = 'Profil Ekle';
$('advancedFormulaPreview').classList.remove('show');
$('advancedFormulaTest').classList.remove('show');
$('advancedFormulaTestResult').classList.remove('show');
updateAdvancedFormulaDescription();
updateAdvancedFormulaPreview();
} else {
camFormulaSection.style.display = 'none';
advancedFormulaSection.style.display = 'none';
reductionField.style.display = 'block';
button.textContent = 'Profil Ekle';
}
}
// Gelişmiş formül açıklamalarını güncelle
function updateAdvancedFormulaDescription() {
const type = $('modalAdvancedFormulaType').value;
const descriptionElement = $('modalAdvancedFormulaDescription');
const partType = $('modalPartType').value;
const dimension = partType === 'yatay' ? 'Genişlikten' : 'Yükseklikten';
let description = '';
switch(type) {
case 'fixed':
description = `Sabit düşüm: ${dimension} sabit bir değer çıkarılır. Örn: 1500mm pencere için 1500 - 20 = 1480mm profil`;
break;
case 'formula':
description = `Matematiksel formül: width (genişlik) ve height (yükseklik) değişkenlerini kullanarak formül yazın. ` +
`Örn: ${partType === 'yatay' ? 'width - (width * 0.01) - 10' : 'height - (height * 0.01) - 10'}`;
break;
case 'custom':
description = `Özel formül: JavaScript fonksiyonu yazın. width ve height değişkenlerini kullanabilirsiniz. ` +
`Örn: ${partType === 'yatay' ? 'Math.floor(width) - 15' : 'Math.ceil(height) - 15'}`;
break;
}
descriptionElement.textContent = description;
}
// Gelişmiş formül önizlemesini güncelle
function updateAdvancedFormulaPreview() {
const formula = $('modalAdvancedFormula').value;
const partType = $('modalPartType').value;
const dimension = partType === 'yatay' ? 'Genişlik' : 'Yükseklik';
$('advancedFormulaPreviewText').innerHTML =
`Profil Ölçüsü = ${formula}<br>(${dimension} baz alınarak hesaplanır)`;
}
// Gelişmiş formül test etme
function testAdvancedFormula() {
const width = parseInt($('advancedTestWidth').value) || 0;
const height = parseInt($('advancedTestHeight').value) || 0;
const partType = $('modalPartType').value;
if (width <= 0 || height <= 0) {
$('advancedFormulaTestResult').innerHTML = 'Lütfen geçerli genişlik ve yükseklik değerleri girin.';
$('advancedFormulaTestResult').classList.add('show');
return;
}
try {
const formula = $('modalAdvancedFormula').value;
const result = evaluateFormula(formula, width, height);
const dimension = partType === 'yatay' ? 'Genişlik' : 'Yükseklik';
$('advancedFormulaTestResult').innerHTML =
`Test Sonuçları:<br>
Pencere: ${width}x${height}mm<br>
${dimension} Profil: ${Math.round(result)}mm<br>
Formül: ${formula}`;
$('advancedFormulaTestResult').classList.add('show');
} catch (error) {
$('advancedFormulaTestResult').innerHTML = `Hata: ${error.message}`;
$('advancedFormulaTestResult').classList.add('show');
}
}
// Cam formül açıklamalarını güncelle
function updateFormulaDescription(direction) {
const type = $(`modalGlass${direction.charAt(0).toUpperCase() + direction.slice(1)}FormulaType`).value;
const descriptionElement = $(`modalGlass${direction.charAt(0).toUpperCase() + direction.slice(1)}Description`);
let description = '';
switch(type) {
case 'fixed':
description = 'Sabit düşüm: ' + (direction === 'horizontal' ? 'Genişlikten' : 'Yükseklikten') +
' sabit bir değer çıkarılır. Örn: 1500mm pencere için 1500 - 20 = 1480mm cam';
break;
case 'formula':
description = 'Matematiksel formül: width (genişlik) ve height (yükseklik) değişkenlerini kullanarak formül yazın. ' +
'Örn: ' + (direction === 'horizontal' ? 'width - (width * 0.01) - 10' : 'height - (height * 0.01) - 10');
break;
case 'custom':
description = 'Özel formül: JavaScript fonksiyonu yazın. width ve height değişkenlerini kullanabilirsiniz. ' +
'Örn: ' + (direction === 'horizontal' ? 'Math.floor(width) - 15' : 'Math.ceil(height) - 15');
break;
}
descriptionElement.textContent = description;
updateFormulaPreview();
}
// Formül önizlemesini güncelle
function updateFormulaPreview() {
const horizontalFormula = $('modalGlassHorizontalFormula').value;
const verticalFormula = $('modalGlassVerticalFormula').value;
$('formulaPreviewText').innerHTML =
`Cam Genişlik = ${horizontalFormula}<br>Cam Yükseklik = ${verticalFormula}`;
}
// Formül test etme
function testGlassFormulas() {
const width = parseInt($('testWidth').value) || 0;
const height = parseInt($('testHeight').value) || 0;
if (width <= 0 || height <= 0) {
$('formulaTestResult').innerHTML = 'Lütfen geçerli genişlik ve yükseklik değerleri girin.';
$('formulaTestResult').classList.add('show');
return;
}
try {
const horizontalFormula = $('modalGlassHorizontalFormula').value;
const verticalFormula = $('modalGlassVerticalFormula').value;
const glassWidth = evaluateFormula(horizontalFormula, width, height);
const glassHeight = evaluateFormula(verticalFormula, width, height);
$('formulaTestResult').innerHTML =
`Test Sonuçları:<br>
Pencere: ${width}x${height}mm<br>
Cam: ${glassWidth}x${glassHeight}mm<br>
Alan: ${((glassWidth * glassHeight) / 1000000).toFixed(2)} m²`;
$('formulaTestResult').classList.add('show');
} catch (error) {
$('formulaTestResult').innerHTML = `Hata: ${error.message}`;
$('formulaTestResult').classList.add('show');
}
}
// Formül değerlendirme (güvenli)
function evaluateFormula(formula, width, height) {
// width ve height değişkenlerini yerine koy
let safeFormula = formula
.replace(/width/g, width)
.replace(/height/g, height);
// Matematiksel fonksiyonları kontrol et
const allowedFunctions = ['Math.floor', 'Math.ceil', 'Math.round', 'Math.abs', 'Math.max', 'Math.min'];
const hasMathFunctions = allowedFunctions.some(func => safeFormula.includes(func));
// Sadece güvenli karakterlere izin ver
if (!/^[0-9+\-*/().\sMathfloorceilroundabsmaxmin]+$/.test(safeFormula) && !hasMathFunctions) {
throw new Error('Formülde izin verilmeyen karakterler var');
}
try {
// Matematik fonksiyonlarını kullanarak değerlendir
const result = eval(safeFormula);
if (isNaN(result) || !isFinite(result)) {
throw new Error('Geçersiz sonuç');
}
return result;
} catch (error) {
throw new Error('Formül değerlendirilemedi: ' + error.message);
}
}
// Test verisi ekleme
const addTestData = () => {
const testSystem = {
id: 'test-system-1',
name: 'PVC Pencere Sistemi',
image: null,
parts: [
{
id: 'part1',
name: 'Üst Profil',
type: 'yatay',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin üst kısmında kullanılan yatay profil',
advancedFormula: {
formula: 'width - 10',
type: 'fixed'
}
},
{
id: 'part2',
name: 'Alt Profil',
type: 'yatay',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin alt kısmında kullanılan yatay profil',
advancedFormula: {
formula: 'width - 10',
type: 'fixed'
}
},
{
id: 'part3',
name: 'Sol Profil',
type: 'dikey',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin sol tarafında kullanılan dikey profil',
advancedFormula: {
formula: 'height - 10',
type: 'fixed'
}
},
{
id: 'part4',
name: 'Sağ Profil',
type: 'dikey',
quantity: 1,
reduction: 10,
image: null,
description: 'Pencerenin sağ tarafında kullanılan dikey profil',
advancedFormula: {
formula: 'height - 10',
type: 'fixed'
}
},
{
id: 'part5',
name: 'Cam',
type: 'cam',
quantity: 1,
reduction: 0,
image: null,
description: '4-16-4 İzolasyon Cam - Çift camlı izolasyon cam ünitesi',
glassFormulas: {
horizontal: 'width - 20',
horizontalType: 'fixed',
vertical: 'height - 20',
verticalType: 'fixed'
}
},
{
id: 'part6',
name: 'Cam 2',
type: 'cam',
quantity: 1,
reduction: 0,
image: null,
description: '6-16-6 Lamine Cam - Güvenlik camı',
glassFormulas: {
horizontal: 'width - 25',
horizontalType: 'fixed',
vertical: 'height - 25',
verticalType: 'fixed'
}
}
]
};
systems.push(testSystem);
saveData();
updateSystemSelect();
customerData['Test Cari'] = {
info: {
phone: '0555 555 55 55',
email: 'test@cari.com',
address: 'Test Adresi',
createdAt: new Date().toISOString()
},
pos: {
1: {
system: testSystem,
width: 1500,
height: 1200,
quantity: 2,
projectName: 'Test Projesi',
customerName: 'Test Cari',
horizontalParts: [
{ name: 'Üst Profil', size: 1490, quantity: 2 },
{ name: 'Alt Profil', size: 1490, quantity: 2 }
],
verticalParts: [
{ name: 'Sol Profil', size: 1190, quantity: 2 },
{ name: 'Sağ Profil', size: 1190, quantity: 2 }
],
glassParts: [
{ name: '4-16-4 İzolasyon Cam', quantity: 2, size: '1480x1180', width: 1480, height: 1180, totalArea: '6.98' },
{ name: '6-16-6 Lamine Cam', quantity: 2, size: '1475x1175', width: 1475, height: 1175, totalArea: '6.92' }
],
glassInfo: { width: 1480, height: 1180, totalArea: '6.98' },
timestamp: new Date().toISOString(),
pozNumber: 1
}
}
};
saveCustomerData();
updateCustomerList();
renderCustomerList();
updateNewCustomerButton();
console.log('Test verileri eklendi!');
};
// Mevcut Sistemler Modalını Açma
const openSystemsModal = () => {
const modal = $('systemsModal');
updateSystemsModalList();
modal.style.display = 'block';
closeSystemModal();
closeProfileModal();
};
const closeSystemsModal = () => {
$('systemsModal').style.display = 'none';
};
// Sistem Modalını Açma
const openSystemModal = (systemId = '') => {
const modal = $('systemModal');
const title = $('systemModalTitle');
const button = $('modalSystemButton');
closeSystemsModal();
if (systemId) {
isEditingSystem = true;
currentSystemId = systemId;
const system = systems.find(s => s.id === systemId);
if (system) {
$('modalSystemName').value = system.name;
$('modalSystemImagePreview').innerHTML = system.image ? `<img src="${system.image}" class="image-preview">` : '';
title.textContent = 'Sistem Düzenle';
button.textContent = 'Sistemi Güncelle';
}
} else {
isEditingSystem = false;
currentSystemId = null;
$('modalSystemName').value = '';
$('modalSystemImage').value = '';
$('modalSystemImagePreview').innerHTML = '';
title.textContent = 'Yeni Sistem Ekle';
button.textContent = 'Sistemi Ekle';
}
modal.style.display = 'block';
};
const closeSystemModal = () => {
$('systemModal').style.display = 'none';
isEditingSystem = false;
currentSystemId = null;
};
// Profil Modalını Açma
const openProfileModal = (systemId = '', partId = '') => {
const modal = $('profileModal');
const systemSelect = $('modalSystemSelect');
const title = $('profileModalTitle');
const button = $('modalProfileButton');
closeSystemsModal();
systemSelect.innerHTML = '<option value="">Sistem Seçin</option>' +
systems.map(s => `<option value="${s.id}" ${s.id === systemId ? 'selected' : ''}>${s.name}</option>`).join('');
if (partId) {
isEditingPart = true;
currentPartId = partId;
const system = systems.find(s => s.id === systemId);
const part = system.parts.find(p => p.id === partId);
if (part) {
$('modalPartDescription').value = part.description || '';
$('modalPartType').value = part.type;
$('modalPartQuantity').value = part.quantity;
$('modalPartReduction').value = part.reduction;
$('modalPartImagePreview').innerHTML = part.image ? `<img src="${part.image}" class="image-preview">` : '';
// Gelişmiş formül verilerini yükle
if (part.advancedFormula) {
$('modalAdvancedFormulaType').value = part.advancedFormula.type || 'fixed';
$('modalAdvancedFormula').value = part.advancedFormula.formula || `width - ${part.reduction}`;
} else {
$('modalAdvancedFormulaType').value = 'fixed';
$('modalAdvancedFormula').value = `width - ${part.reduction}`;
}
// Cam formül verilerini yükle
if (part.type === 'cam' && part.glassFormulas) {
$('modalGlassHorizontalFormula').value = part.glassFormulas.horizontal || 'width - 20';
$('modalGlassHorizontalFormulaType').value = part.glassFormulas.horizontalType || 'fixed';
$('modalGlassVerticalFormula').value = part.glassFormulas.vertical || 'height - 20';
$('modalGlassVerticalFormulaType').value = part.glassFormulas.verticalType || 'fixed';
updateFormulaDescription('horizontal');
updateFormulaDescription('vertical');
}
toggleFormulaSections();
title.textContent = part.type === 'cam' ? 'Cam Düzenle' : 'Profil Düzenle';
button.textContent = part.type === 'cam' ? 'Cam Güncelle' : 'Profili Güncelle';
}
} else {
isEditingPart = false;
currentPartId = null;
$('modalPartDescription').value = '';
$('modalPartType').value = 'yatay';
$('modalPartQuantity').value = '1';
$('modalPartReduction').value = '0';
$('modalPartImage').value = '';
$('modalPartImagePreview').innerHTML = '';
$('modalAdvancedFormulaType').value = 'fixed';
$('modalAdvancedFormula').value = 'width - 0';
$('modalGlassHorizontalFormula').value = 'width - 20';
$('modalGlassHorizontalFormulaType').value = 'fixed';
$('modalGlassVerticalFormula').value = 'height - 20';
$('modalGlassVerticalFormulaType').value = 'fixed';
toggleFormulaSections();
title.textContent = 'Profil Ekle';
button.textContent = 'Profil Ekle';
}
modal.style.display = 'block';
};
const closeProfileModal = () => {
$('profileModal').style.display = 'none';
isEditingPart = false;
currentPartId = null;
};
// Mevcut Sistemler Modal Listesini Güncelleme
const updateSystemsModalList = () => {
const systemsListContainer = $('systemsListContainer');
if (systems.length === 0) {
systemsListContainer.innerHTML = '<div class="empty-state">Henüz sistem yok</div>';
return;
}
let html = '<div class="systems-list">';
systems.forEach(system => {
const profileCount = system.parts.length;
const systemPartsId = `parts-${system.id}`;
html += `
<div class="system-item">
${system.image ? `<img src="${system.image}" class="system-item-image">` : ''}
<div class="system-item-name">${system.name}</div>
<div class="system-item-parts">${profileCount} profil</div>
<div class="system-item-actions">
<button class="edit-btn" onclick="openSystemModal('${system.id}')">Düzenle</button>
<button class="catalog-btn" onclick="copySystem('${system.id}')">Kopyala</button>
<button class="add-btn" onclick="openProfileModal('${system.id}')">Profil Ekle</button>
<button class="edit-btn" onclick="togglePartsList('${system.id}')">Profiller</button>
<button class="delete-btn" onclick="deleteSystem('${system.id}')">Sil</button>
</div>
<div id="${systemPartsId}" class="parts-list">
<h6 style="margin-bottom: 8px; color: #4a5568; text-align: left; font-size: 13px;">Sistem Profilleri:</h6>
${system.parts.length === 0 ?
'<div class="empty-state" style="font-size: 11px;">Henüz profil yok</div>' :
system.parts.map(p => `
<div class="part-details">
<div class="part-info">
${p.image ? `<img src="${p.image}" class="part-image" style="max-width: 30px; max-height: 30px;">` : ''}
<div style="font-size: 12px;">
<strong>${p.name}</strong><br>
<small>${p.type === 'yatay' ? 'Yatay' : p.type === 'dikey' ? 'Dikey' : 'Cam'} | ${p.quantity} adet ${p.type !== 'cam' ? '| ' + p.reduction + 'mm' : ''}</small>
${p.description ? `<br><small style="color: #718096;">${p.description}</small>` : ''}
${p.advancedFormula ? `<br><small style="color: #38a169;">📐 Formül: ${p.advancedFormula.formula}</small>` : ''}
${p.glassFormulas ? `<br><small style="color: #2b6cb0;">🔍 Cam Formülü: ${p.glassFormulas.horizontal} x ${p.glassFormulas.vertical}</small>` : ''}
</div>
</div>
<div class="part-actions">
<button class="edit-btn" onclick="openProfileModal('${system.id}', '${p.id}')">Düz.</button>
<button class="delete-btn" onclick="deletePart('${system.id}', '${p.id}')">Sil</button>
</div>
</div>
`).join('')
}
</div>
</div>`;
});
html += '</div>';
systemsListContainer.innerHTML = html;
};
// Profil listesini aç/kapa
const togglePartsList = (systemId) => {
const partsList = document.getElementById(`parts-${systemId}`);
if (partsList) {
partsList.classList.toggle('show');
}
};
// Resim işlemleri
const previewImage = (input, previewId) => {
$(previewId).innerHTML = input.files[0] ? `<img src="${URL.createObjectURL(input.files[0])}" class="image-preview">` : '';
};
const saveSystemFromModal = () => {
const systemData = {
name: $('modalSystemName').value,
image: null
};
if (!systemData.name) return alert('Sistem adı gerekli!');
const file = $('modalSystemImage').files[0];
const processSystem = image => {
systemData.image = image;
if (isEditingSystem && currentSystemId) {
const systemIndex = systems.findIndex(s => s.id === currentSystemId);
if (systemIndex !== -1) {
systems[systemIndex] = {
...systems[systemIndex],
name: systemData.name,
image: systemData.image
};
}
} else {
const newSystem = {
id: Date.now().toString(),
...systemData,
parts: []
};
systems.push(newSystem);
}
if (saveData()) {
updateSystemSelect();
closeSystemModal();
openSystemsModal();
alert(isEditingSystem ? 'Sistem başarıyla güncellendi!' : 'Sistem başarıyla eklendi!');
}
};
if (file) {
const reader = new FileReader();
reader.onload = e => processSystem(e.target.result);
reader.readAsDataURL(file);
} else {
processSystem(isEditingSystem ? systems.find(s => s.id === currentSystemId)?.image || null : null);
}
};
const addPartFromModal = () => {
const systemId = $('modalSystemSelect').value;
const partType = $('modalPartType').value;
const partDescription = $('modalPartDescription').value;
if (!systemId) return alert('Lütfen bir sistem seçin!');
if (!partDescription) return alert('Lütfen profil açıklaması girin!');
let partName = '';
switch(partType) {
case 'yatay':
partName = 'Yatay Profil';
break;
case 'dikey':
partName = 'Dikey Profil';
break;
case 'cam':
partName = 'Cam';
break;
}
const partData = {
name: partName,
description: partDescription,
type: partType,
quantity: parseInt($('modalPartQuantity').value) || 1,
reduction: partType === 'cam' ? 0 : parseInt($('modalPartReduction').value) || 0,
image: null
};
// Gelişmiş formül verilerini ekle
if (partType === 'yatay' || partType === 'dikey') {
partData.advancedFormula = {
formula: $('modalAdvancedFormula').value,
type: $('modalAdvancedFormulaType').value
};
}
// Cam formül verilerini ekle
if (partType === 'cam') {
partData.glassFormulas = {
horizontal: $('modalGlassHorizontalFormula').value,
horizontalType: $('modalGlassHorizontalFormulaType').value,
vertical: $('modalGlassVerticalFormula').value,
verticalType: $('modalGlassVerticalFormulaType').value
};
}
const system = systems.find(s => s.id === systemId);
const file = $('modalPartImage').files[0];
const processPart = image => {
partData.image = image;
if (isEditingPart && currentPartId) {
const partIndex = system.parts.findIndex(p => p.id === currentPartId);
if (partIndex !== -1) {
system.parts[partIndex] = { ...system.parts[partIndex], ...partData };
}
} else {
const newPart = {
id: Date.now().toString(),
...partData
};
system.parts.push(newPart);
}
if (saveData()) {
closeProfileModal();
openSystemsModal();
alert(isEditingPart ? (partType === 'cam' ? 'Cam başarıyla güncellendi!' : 'Profil başarıyla güncellendi!') :
(partType === 'cam' ? 'Cam başarıyla eklendi!' : 'Profil başarıyla eklendi!'));
}
};
if (file) {
const reader = new FileReader();
reader.onload = e => processPart(e.target.result);
reader.readAsDataURL(file);
} else {
processPart(isEditingPart ? system.parts.find(p => p.id === currentPartId)?.image || null : null);
}
};
// Sistem işlemleri
const selectSystemForCalculation = systemId => {
const system = systems.find(s => s.id === systemId);
if (system) {
currentSystemId = systemId;
$('systemPreview').innerHTML = `
${system.image ? `<img src="${system.image}" class="system-image">` : ''}
<div>
<strong>${system.name}</strong>
<div><small>${system.parts.length} profil</small></div>
</div>
`;
$('selectedSystemPreview').style.display = 'block';
}
};
// Sistem silme
const deleteSystem = systemId => {
if (!confirm('Bu sistemi ve tüm profillerini silmek istediğinizden emin misiniz?')) return;
systems = systems.filter(s => s.id !== systemId);
if (saveData()) {
updateSystemSelect();
updateSystemsModalList();
}
};
// Profil silme
const deletePart = (systemId, partId) => {
if (!confirm('Bu profili silmek istediğinizden emin misiniz?')) return;
const system = systems.find(s => s.id === systemId);
if (system) {
system.parts = system.parts.filter(p => p.id !== partId);
if (saveData()) {
updateSystemsModalList();
}
}
};
// Sistem kopyalama
const copySystem = (systemId) => {
const originalSystem = systems.find(s => s.id === systemId);
if (!originalSystem) return;
// Yeni sistem adı iste
const copyName = prompt('Kopyalanacak sistemin yeni adını girin:', originalSystem.name + ' - Kopya');
if (!copyName || copyName.trim() === '') return;
// Sistem adı kontrolü
if (systems.some(s => s.name === copyName.trim())) {
alert('Bu isimde bir sistem zaten var!');
return;
}
// Sistemin kopyasını oluştur
const copiedSystem = {
id: Date.now().toString(),
name: copyName.trim(),
image: originalSystem.image,
parts: originalSystem.parts.map(part => ({
...part,
id: Date.now().toString() + Math.random().toString(36).substr(2, 9), // Her profil için yeni ID
// Resimleri kopyala (varsa)
image: part.image || null
}))
};
// Sistemi ekle
systems.push(copiedSystem);
if (saveData()) {
updateSystemSelect();
updateSystemsModalList();
alert(`"${originalSystem.name}" sistemi başarıyla kopyalandı ve "${copiedSystem.name}" olarak eklendi!`);
} else {
alert('Sistem kopyalanırken bir hata oluştu!');
}
};
// UI güncellemeleri
const updateSystemSelect = () => {
$('systemSelect').innerHTML = '<option value="">Sistem seçin...</option>' +
systems.map(s => `<option value="${s.id}">${s.name}</option>`).join('');
};
// Cari işlemleri
const addNewCustomer = () => {
const name = $('modalNewCustomerName').value.trim();
const phone = $('modalNewCustomerPhone').value.trim();
const email = $('modalNewCustomerEmail').value.trim();
const address = $('modalNewCustomerAddress').value.trim();
if (!name) {
alert('Cari adı gerekli!');
return;
}
if (customerData[name]) {
alert('Bu isimde bir cari zaten var!');
return;
}
customerData[name] = {
info: {
phone,
email,
address,
createdAt: new Date().toISOString()
},
pos: {}
};
if (saveCustomerData()) {
updateCustomerList();
renderCustomerList();
updateNewCustomerButton();
closeAddCustomerModal();
alert('Cari başarıyla eklendi!');
}
};
const updateCustomerList = () => {
const customerList = $('customerList');
customerList.innerHTML = '';
Object.keys(customerData).forEach(customerName => {
const option = document.createElement('option');
option.value = customerName;
customerList.appendChild(option);
});
};
const renderCustomerList = () => {
const container = $('customerListContainer');
if (Object.keys(customerData).length === 0) {
container.innerHTML = '<div class="empty-state">Henüz cari yok</div>';
return;
}
let html = '';
Object.entries(customerData).forEach(([name, data]) => {
const posCount = Object.keys(data.pos).length;
const isSelected = selectedCustomer === name ? 'style="background-color: #f0f7ff; border-color: #4a90e2;"' : '';
html += `
<div class="customer-item" ${isSelected} onclick="selectCustomer('${name}')">
<div>
<strong>${name}</strong><br>
<small>${data.info.phone} | ${posCount} poz</small>
</div>
<div>
<button class="edit-btn" onclick="event.stopPropagation(); editCustomer('${name}')">Düzenle</button>
<button class="delete-btn" onclick="event.stopPropagation(); deleteCustomer('${name}')">Sil</button>
</div>
</div>`;
});
container.innerHTML = html;
};
const selectCustomer = (customerName) => {
selectedCustomer = customerName;
renderCustomerList();
loadCustomerData();
};
const filterCustomers = () => {
const search = $('customerSearch').value.toLowerCase();
const items = $('customerListContainer').getElementsByClassName('customer-item');
Array.from(items).forEach(item => {
const name = item.querySelector('strong').textContent.toLowerCase();
item.style.display = name.includes(search) ? 'flex' : 'none';
});
};
const loadCustomerData = () => {
const posContainer = $('customerPosContainer');
if (!selectedCustomer) {
posContainer.innerHTML = '<div class="empty-state">Cari seçmek için listeden bir cariye tıklayın</div>';
return;
}
const customer = customerData[selectedCustomer];
if (Object.keys(customer.pos).length === 0) {
posContainer.innerHTML = `
<div class="empty-state">
Bu cari için henüz poz yok<br>
<button class="download-all-btn" onclick="generateAllCustomerPosPDF('${selectedCustomer}')">
📥 Tüm Pozları PDF Olarak İndir
</button>
</div>`;
return;
}
let html = `
<div class="customer-pos-list">
<div class="pos-selection-controls">
<button class="select-all-btn" onclick="selectAllPositions()">Tümünü Seç</button>
<button class="deselect-all-btn" onclick="deselectAllPositions()">Seçimi Temizle</button>
<button class="generate-selected-btn" onclick="generateSelectedPosPDF()">Seçilen Pozları PDF Oluştur</button>
<button class="pdf-btn" onclick="generateSelectedPosImagesPDF()">📷 Sadece Resimleri PDF</button>
<button class="download-all-btn" onclick="generateAllCustomerPosPDF('${selectedCustomer}')">
📥 Tüm Pozları PDF Olarak İndir
</button>
<div class="selected-count" id="selectedCount">Seçilen: 0</div>
</div>
`;
Object.values(customer.pos).forEach(pos => {
const isSelected = selectedPositions.has(pos.pozNumber);
// Tüm camları al
const camParts = pos.system.parts.filter(p => p.type === 'cam');
let camInfoHtml = '';
if (camParts.length > 0) {
camParts.forEach(camPart => {
// Her cam için doğru ölçüleri göster
let camWidth = pos.width - 20;
let camHeight = pos.height - 20;
if (camPart.glassFormulas) {
try {
camWidth = evaluateFormula(camPart.glassFormulas.horizontal, pos.width, pos.height);
camHeight = evaluateFormula(camPart.glassFormulas.vertical, pos.width, pos.height);
} catch (error) {
console.error('Cam formülü hesaplanırken hata:', error);
}
}
camInfoHtml += `<div>${camPart.description}: ${Math.round(camWidth)}x${Math.round(camHeight)}mm (${camPart.quantity * pos.quantity} adet)</div>`;
});
} else {
camInfoHtml = `<div>${pos.glassInfo.width}x${pos.glassInfo.height}mm (${pos.quantity} adet)</div>`;
}
html += `
<div class="customer-pos-item" style="${isSelected ? 'background-color: #f0f7ff; border-color: #4a90e2;' : ''}">
<div class="customer-pos-info">
<div class="customer-header">
<div>
<input type="checkbox" class="pos-checkbox" ${isSelected ? 'checked' : ''}
onchange="togglePositionSelection(${pos.pozNumber}, this.checked)">
<strong>Poz #${pos.pozNumber} - ${pos.projectName || 'Projesiz'}</strong>
</div>
<small>${new Date(pos.timestamp).toLocaleDateString('tr-TR')}</small>
</div>
<div>
<strong>Sistem:</strong> ${pos.system.name}<br>
<strong>Ölçüler:</strong> ${pos.width}x${pos.height}mm - ${pos.quantity} adet<br>
<strong>Toplam Alan:</strong> ${pos.glassInfo.totalArea}m²<br>
<strong>Cam:</strong> ${camInfoHtml}
</div>
</div>
<div class="customer-pos-actions">
<button class="edit-btn" onclick="editCustomerPos('${selectedCustomer}', ${pos.pozNumber})">Düzenle</button>
<button class="pdf-btn" onclick="generateCustomerPosPDF('${selectedCustomer}', ${pos.pozNumber})">PDF</button>
<button class="delete-btn" onclick="deleteCustomerPos('${selectedCustomer}', ${pos.pozNumber})">Sil</button>
</div>
</div>`;
});
html += '</div>';
posContainer.innerHTML = html;
updateSelectedCount();
};
// Poz seçim işlevleri
function togglePositionSelection(pozNumber, isSelected) {
if (isSelected) {
selectedPositions.add(pozNumber);
} else {
selectedPositions.delete(pozNumber);
}
updateSelectedCount();
}
function selectAllPositions() {
const customer = customerData[selectedCustomer];
if (customer && customer.pos) {
Object.values(customer.pos).forEach(pos => {
selectedPositions.add(pos.pozNumber);
});
loadCustomerData(); // Listeyi yeniden yükle
}
}
function deselectAllPositions() {
selectedPositions.clear();
loadCustomerData(); // Listeyi yeniden yükle
}
function updateSelectedCount() {
const selectedCountElement = document.getElementById('selectedCount');
if (selectedCountElement) {
selectedCountElement.textContent = `Seçilen: ${selectedPositions.size}`;
}
}
// Seçilen pozları PDF olarak oluştur - GÜNCELLENDİ: Başlık sayfası kaldırıldı
function generateSelectedPosPDF() {
if (selectedPositions.size === 0) {
alert('Lütfen en az bir poz seçin!');
return;
}
const customer = customerData[selectedCustomer];
const selectedPosList = Object.values(customer.pos).filter(pos =>
selectedPositions.has(pos.pozNumber)
);
if (selectedPosList.length === 0) {
alert('Seçilen pozlar bulunamadı!');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
setupPDFFont(doc);
let currentPage = 1;
// Başlık sayfası kaldırıldı - doğrudan pozlara başla
const groupedByProject = {};
selectedPosList.forEach(pos => {
const projectName = pos.projectName || 'Projesiz';
if (!groupedByProject[projectName]) {
groupedByProject[projectName] = [];
}
groupedByProject[projectName].push(pos);
});
Object.entries(groupedByProject).forEach(([projectName, positions]) => {
// Proje başlığı
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars(`PROJE: ${projectName}`), 20, 20);
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
doc.text(`Sayfa: ${currentPage}`, 190, 20, { align: 'right' });
let yPos = 35;
positions.forEach((pos, index) => {
if (yPos > 250) {
doc.addPage();
currentPage++;
yPos = 20;
}
// Poz başlığı
doc.setFontSize(11);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars(`Poz #${pos.pozNumber}`), 20, yPos);
yPos += 6;
// Temel bilgiler
doc.setFontSize(9);
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars(`Sistem: ${pos.system.name}`), 20, yPos);
doc.text(fixTurkishChars(`Ölçüler: ${pos.width}x${pos.height}mm - ${pos.quantity} adet`), 100, yPos);
yPos += 4;
doc.text(fixTurkishChars(`Toplam Alan: ${pos.glassInfo.totalArea}m²`), 20, yPos);
doc.text(fixTurkishChars(`Tarih: ${new Date(pos.timestamp).toLocaleDateString('tr-TR')}`), 100, yPos);
yPos += 6;
// Profil listesi başlığı - MM ve ADET sütunları yer değiştirdi
doc.setFontSize(8);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('AÇIKLAMA'), 20, yPos);
doc.text(fixTurkishChars('MM'), 120, yPos); // MM sütunu ADET'ten önce
doc.text(fixTurkishChars('ADET'), 160, yPos); // ADET sütunu MM'den sonra
yPos += 3;
doc.line(20, yPos, 190, yPos);
yPos += 2;
// Yatay profiller
if (pos.horizontalParts.length > 0) {
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('YATAY PROFİLLER'), 20, yPos);
yPos += 3;
pos.horizontalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
currentPage++;
yPos = 20;
}
doc.setFont(undefined, 'normal');
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.text(fixTurkishChars(description), 20, yPos);
doc.text(part.size.toString(), 120, yPos); // MM değeri
doc.text(part.quantity.toString(), 160, yPos); // ADET değeri
yPos += 3;
});
yPos += 1;
}
// Dikey profiller
if (pos.verticalParts.length > 0) {
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('DİKEY PROFİLLER'), 20, yPos);
yPos += 3;
pos.verticalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
currentPage++;
yPos = 20;
}
doc.setFont(undefined, 'normal');
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.text(fixTurkishChars(description), 20, yPos);
doc.text(part.size.toString(), 120, yPos); // MM değeri
doc.text(part.quantity.toString(), 160, yPos); // ADET değeri
yPos += 3;
});
yPos += 1;
}
// TÜM CAMLARI GÖSTER - MM ve ADET sütunları yer değiştirdi
const camParts = pos.system.parts.filter(p => p.type === 'cam');
if (camParts.length > 0) {
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('CAM BİLGİSİ'), 20, yPos);
yPos += 3;
camParts.forEach(camPart => {
if (yPos > 270) {
doc.addPage();
currentPage++;
yPos = 20;
}
// Cam açıklaması
const camDescription = camPart.description || 'Cam';
doc.setFont(undefined, 'normal');
const camDescShort = camDescription.length > 35 ? camDescription.substring(0, 35) + '...' : camDescription;
doc.text(fixTurkishChars(camDescShort), 20, yPos);
// HER CAM İÇİN DOĞRU ÖLÇÜLERİ HESAPLA
let camWidth = pos.width - 20;
let camHeight = pos.height - 20;
// Eğer camın kendi formülü varsa onu kullan
if (camPart.glassFormulas) {
try {
camWidth = evaluateFormula(camPart.glassFormulas.horizontal, pos.width, pos.height);
camHeight = evaluateFormula(camPart.glassFormulas.vertical, pos.width, pos.height);
} catch (error) {
console.error('Cam formülü hesaplanırken hata:', error);
}
}
doc.text(`${Math.round(camWidth)}x${Math.round(camHeight)}`, 120, yPos); // MM değeri
doc.text((camPart.quantity * pos.quantity).toString(), 160, yPos); // ADET değeri
yPos += 3;
});
}
// Ayırıcı çizgi
if (index < positions.length - 1) {
doc.line(20, yPos, 190, yPos);
yPos += 5;
}
});
currentPage++;
if (Object.entries(groupedByProject).length > 1) {
doc.addPage();
}
});
doc.save(fixTurkishChars(`${selectedCustomer}-secilen-pozlar.pdf`));
}
// Seçilen pozların sadece resimlerini PDF olarak oluştur
function generateSelectedPosImagesPDF() {
if (selectedPositions.size === 0) {
alert('Lütfen en az bir poz seçin!');
return;
}
const customer = customerData[selectedCustomer];
const selectedPosList = Object.values(customer.pos).filter(pos =>
selectedPositions.has(pos.pozNumber)
);
if (selectedPosList.length === 0) {
alert('Seçilen pozlar bulunamadı!');
return;
}
// Sadece resimleri olan pozları filtrele
const positionsWithImages = selectedPosList.filter(pos => pos.system && pos.system.image);
if (positionsWithImages.length === 0) {
alert('Seçilen pozlarda sistem resmi bulunamadı!');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
setupPDFFont(doc);
let currentPage = 1;
let totalPages = 1;
// PDF başlığı
let yPos = addCompanyHeaderToPDF(doc, currentPage, totalPages);
doc.setFontSize(16);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars(`Sistem Resimleri - ${selectedCustomer}`), 105, yPos + 5, { align: 'center' });
yPos += 20;
positionsWithImages.forEach((pos, index) => {
// Her poz için resim boyutunu hesapla
const imgWidth = 80;
const imgHeight = 80;
const pageWidth = 210; // A4 genişlik
const pageHeight = 297; // A4 yükseklik
const margin = 20;
// Eğer resim sayfaya sığmayacak kadar büyükse yeni sayfa ekle
if (yPos + imgHeight + 40 > pageHeight) {
doc.addPage();
currentPage++;
yPos = addCompanyHeaderToPDF(doc, currentPage, totalPages);
}
// Poz bilgileri
doc.setFontSize(12);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars(`Poz #${pos.pozNumber}`), margin, yPos);
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars(`Proje: ${pos.projectName || 'Projesiz'}`), margin, yPos + 6);
doc.text(fixTurkishChars(`Sistem: ${pos.system.name}`), margin, yPos + 12);
doc.text(fixTurkishChars(`Ölçüler: ${pos.width}x${pos.height}mm`), margin, yPos + 18);
// Resmi ekle
const imgX = (pageWidth - imgWidth) / 2;
const imgY = yPos + 25;
try {
doc.addImage(pos.system.image, 'JPEG', imgX, imgY, imgWidth, imgHeight);
} catch (error) {
console.error('Resim PDF\'e eklenemedi:', error);
doc.setFontSize(8);
doc.text(fixTurkishChars('Resim yüklenemedi'), imgX, imgY + 40);
}
// Resim açıklaması
doc.setFontSize(8);
doc.text(fixTurkishChars(`${pos.system.name} - ${pos.width}x${pos.height}mm`), pageWidth / 2, imgY + imgHeight + 8, { align: 'center' });
yPos = imgY + imgHeight + 20;
});
doc.save(fixTurkishChars(`${selectedCustomer}-secilen-sistem-resimleri.pdf`));
}
// Yeni fonksiyon: Tüm cari pozlarını PDF olarak indir + Firma bilgileri eklendi
function generateAllCustomerPosPDF(customerName) {
const customer = customerData[customerName];
if (!customer || Object.keys(customer.pos).length === 0) {
alert('Bu cari için poz bulunamadı!');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
setupPDFFont(doc);
let currentPage = 1;
let totalPages = 1;
const groupedByProject = {};
Object.values(customer.pos).forEach(pos => {
const projectName = pos.projectName || 'Projesiz';
if (!groupedByProject[projectName]) {
groupedByProject[projectName] = [];
}
groupedByProject[projectName].push(pos);
});
Object.entries(groupedByProject).forEach(([projectName, positions]) => {
// Firma bilgilerini her sayfaya ekle
let yPos = addCompanyHeaderToPDF(doc, currentPage, totalPages);
// Proje başlığı
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars(`PROJE: ${projectName}`), 20, yPos);
yPos += 10;
positions.forEach((pos, index) => {
if (yPos > 250) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, ++currentPage, totalPages);
}
// Poz başlığı
doc.setFontSize(11);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars(`Poz #${pos.pozNumber}`), 20, yPos);
yPos += 6;
// Temel bilgiler
doc.setFontSize(9);
doc.setFont(undefined, 'normal');
doc.text(fixTurkishChars(`Sistem: ${pos.system.name}`), 20, yPos);
doc.text(fixTurkishChars(`Ölçüler: ${pos.width}x${pos.height}mm - ${pos.quantity} adet`), 100, yPos);
yPos += 4;
doc.text(fixTurkishChars(`Toplam Alan: ${pos.glassInfo.totalArea}m²`), 20, yPos);
doc.text(fixTurkishChars(`Tarih: ${new Date(pos.timestamp).toLocaleDateString('tr-TR')}`), 100, yPos);
yPos += 6;
// Profil listesi başlığı - MM ve ADET sütunları yer değiştirdi
doc.setFontSize(8);
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('AÇIKLAMA'), 20, yPos);
doc.text(fixTurkishChars('MM'), 120, yPos); // MM sütunu ADET'ten önce
doc.text(fixTurkishChars('ADET'), 160, yPos); // ADET sütunu MM'den sonra
yPos += 3;
doc.line(20, yPos, 190, yPos);
yPos += 2;
// Yatay profiller
if (pos.horizontalParts.length > 0) {
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('YATAY PROFİLLER'), 20, yPos);
yPos += 3;
pos.horizontalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, ++currentPage, totalPages);
}
doc.setFont(undefined, 'normal');
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.text(fixTurkishChars(description), 20, yPos);
doc.text(part.size.toString(), 120, yPos); // MM değeri
doc.text(part.quantity.toString(), 160, yPos); // ADET değeri
yPos += 3;
});
yPos += 1;
}
// Dikey profiller
if (pos.verticalParts.length > 0) {
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('DİKEY PROFİLLER'), 20, yPos);
yPos += 3;
pos.verticalParts.forEach(part => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, ++currentPage, totalPages);
}
doc.setFont(undefined, 'normal');
const description = part.name.length > 35 ? part.name.substring(0, 35) + '...' : part.name;
doc.text(fixTurkishChars(description), 20, yPos);
doc.text(part.size.toString(), 120, yPos); // MM değeri
doc.text(part.quantity.toString(), 160, yPos); // ADET değeri
yPos += 3;
});
yPos += 1;
}
// TÜM CAMLARI GÖSTER - MM ve ADET sütunları yer değiştirdi
const camParts = pos.system.parts.filter(p => p.type === 'cam');
if (camParts.length > 0) {
doc.setFont(undefined, 'bold');
doc.text(fixTurkishChars('CAM BİLGİSİ'), 20, yPos);
yPos += 3;
camParts.forEach(camPart => {
if (yPos > 270) {
doc.addPage();
yPos = addCompanyHeaderToPDF(doc, ++currentPage, totalPages);
}
// Cam açıklaması
const camDescription = camPart.description || 'Cam';
doc.setFont(undefined, 'normal');
const camDescShort = camDescription.length > 35 ? camDescription.substring(0, 35) + '...' : camDescription;
doc.text(fixTurkishChars(camDescShort), 20, yPos);
// HER CAM İÇİN DOĞRU ÖLÇÜLERİ HESAPLA
let camWidth = pos.width - 20;
let camHeight = pos.height - 20;
// Eğer camın kendi formülü varsa onu kullan
if (camPart.glassFormulas) {
try {
camWidth = evaluateFormula(camPart.glassFormulas.horizontal, pos.width, pos.height);
camHeight = evaluateFormula(camPart.glassFormulas.vertical, pos.width, pos.height);
} catch (error) {
console.error('Cam formülü hesaplanırken hata:', error);
}
}
doc.text(`${Math.round(camWidth)}x${Math.round(camHeight)}`, 120, yPos); // MM değeri
doc.text((camPart.quantity * pos.quantity).toString(), 160, yPos); // ADET değeri
yPos += 3;
});
}
// Ayırıcı çizgi
if (index < positions.length - 1) {
doc.line(20, yPos, 190, yPos);
yPos += 5;
}
});
currentPage++;
if (Object.entries(groupedByProject).length > 1) {
doc.addPage();
}
});
doc.save(fixTurkishChars(`${customerName}-tum-pozlar.pdf`));
}
// Firma bilgileri fonksiyonları
function openCompanyInfoModal() {
$('companyInfoModal').style.display = 'block';
// Mevcut verileri modal'a yükle
$('modalCompanyName').value = companyData.name;
$('modalCompanyAddress').value = companyData.address;
$('modalCompanyPhone').value = companyData.phone;
$('modalCompanyEmail').value = companyData.email;
$('modalCompanyWebsite').value = companyData.website;
$('modalCompanyDescription').value = companyData.description;
$('modalCompanyCreatedDate').value = new Date(companyData.createdAt).toLocaleDateString('tr-TR');
// Logo önizlemesi
updateCompanyLogoPreview();
}
function closeCompanyInfoModal() {
$('companyInfoModal').style.display = 'none';
}
function updateCompanyLogoPreview() {
const preview = $('companyLogoPreview');
const input = $('modalCompanyLogo');
preview.innerHTML = companyData.logo ?
`<img src="${companyData.logo}" alt="Firma Logosu">` :
`<div class="company-logo-placeholder">Logo seçilmedi</div>`;
}
function previewCompanyLogo(input) {
const preview = $('companyLogoPreview');
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
preview.innerHTML = `<img src="${e.target.result}" alt="Firma Logosu">`;
};
reader.readAsDataURL(input.files[0]);
} else {
updateCompanyLogoPreview();
}
}
function saveCompanyInfo() {
const companyInfo = {
name: $('modalCompanyName').value.trim() || 'Firma Adı',
address: $('modalCompanyAddress').value.trim(),
phone: $('modalCompanyPhone').value.trim(),
email: $('modalCompanyEmail').value.trim(),
website: $('modalCompanyWebsite').value.trim(),
description: $('modalCompanyDescription').value.trim(),
createdAt: companyData.createdAt
};
const logoFile = $('modalCompanyLogo').files[0];
const processCompany = logoImage => {
companyInfo.logo = logoImage;
companyData = companyInfo;
if (saveCompanyData()) {
updateCompanyInfoDisplay();
closeCompanyInfoModal();
alert('Firma bilgileri başarıyla kaydedildi!');
}
};
if (logoFile) {
const reader = new FileReader();
reader.onload = e => processCompany(e.target.result);
reader.readAsDataURL(logoFile);
} else {
processCompany(companyData.logo);
}
}
function saveCompanyData() {
try {
localStorage.setItem('companyData', JSON.stringify(companyData));
updateStorageStatus();
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('Depolama alanı doldu! Lütfen resim boyutlarını küçültün.');
return false;
}
throw e;
}
}
function updateCompanyInfoDisplay() {
$('companyNameDisplay').textContent = companyData.name;
$('companyAddressDisplay').textContent = companyData.address || 'Adres belirtilmemiş';
let contactInfo = [];
if (companyData.phone) contactInfo.push(`📞 ${companyData.phone}`);
if (companyData.email) contactInfo.push(`✉️ ${companyData.email}`);
$('companyContactDisplay').textContent = contactInfo.length > 0 ? contactInfo.join(' | ') : 'İletişim bilgisi belirtilmemiş';
$('companyWebsiteDisplay').textContent = companyData.website ? `🌐 ${companyData.website}` : 'Website belirtilmemiş';
$('companyDescriptionDisplay').textContent = companyData.description || 'Açıklama belirtilmemiş';
const logoDisplay = $('companyLogoDisplay');
logoDisplay.innerHTML = companyData.logo ? `<img src="${companyData.logo}" alt="Firma Logosu">` : '<div style="color: #a0aec0; font-size: 12px;">Logo yok</div>';
}
// Navbar'daki logo'yu güncelle
function updateNavbarLogo() {
const navbarLogo = $('navCompanyLogo');
if (navbarLogo) {
if (companyData.logo) {
navbarLogo.src = companyData.logo;
navbarLogo.style.display = 'block';
} else {
navbarLogo.style.display = 'none';
}
}
}
function previewCompanyInfo() {
const preview = $('companyInfoPreview');
updateCompanyInfoDisplay();
preview.style.display = preview.style.display === 'none' ? 'block' : 'none';
}
// Sayfa tipini belirleme fonksiyonu
function determinePageType(pageNumber, totalPages, currentPage, isFirstPage) {
// Sayfa bazlı ayarlar kullanılıyorsa
if (pdfSettings.usePerPageSettings) {
if (isFirstPage) return 'title';
else if (pageNumber === currentPage) return 'header';
else if (currentPage === totalPages) return 'summary';
else return 'content';
} else {
// Global ayarlar kullanılıyor
return 'global';
}
}
// Sayfa tipine göre ayarları getirme
function getSettingsForPageType(pageType) {
if (pageType === 'global') {
return pdfSettings; // Global ayarları döndür
}
// Sayfa tipine göre ayarları al
const pageSettings = perPageSettings.pages[pageType];
if (!pageSettings) {
return pdfSettings; // Eğer ayar yoksa global ayarları kullan
}
// Global ayarları sayfa ayarlarıyla birleştir
return {
...pdfSettings,
// Logo ayarları - sayfa tipinden
logoWidth: pageSettings.logoWidth,
logoHeight: pageSettings.logoHeight,
logoPosition: pageSettings.logoPosition,
logoTopMargin: pageSettings.logoTopMargin,
// Yazı boyutları - sayfa tipinden
headerFontSize: pageSettings.headerFontSize,
subheaderFontSize: pageSettings.subheaderFontSize,
normalFontSize: pageSettings.normalFontSize,
smallFontSize: pageSettings.smallFontSize,
// Çizgi ayarları - sayfa tipinden
separatorTopMargin: pageSettings.separatorTopMargin,
separatorThickness: pageSettings.separatorThickness
};
}
// PDF'e firma bilgileri ekleme - Sayfa tipine göre ayarlar kullanarak
function addCompanyHeaderToPDF(doc, pageNumber, totalPages) {
const isFirstPage = pageNumber === 1;
const pageType = determinePageType(pageNumber, totalPages, pageNumber, isFirstPage);
const settings = getSettingsForPageType(pageType);
// Logo ayarları - sayfa tipine göre kullan
if (settings.showCompanyLogo && companyData.logo) {
try {
const imgWidth = settings.logoWidth;
const imgHeight = settings.logoHeight;
// Logo konumu - sayfa tipine göre
let logoX = 15;
let logoY = settings.logoTopMargin;
switch(settings.logoPosition) {
case 'center':
logoX = 105 - (imgWidth / 2);
break;
case 'right':
logoX = 195 - imgWidth;
break;
}
doc.addImage(companyData.logo, 'JPEG', logoX, logoY, imgWidth, imgHeight);
} catch (error) {
console.error('Firma logosu PDF\'e eklenemedi:', error);
}
}
// Firma adı - sayfa tipine göre yazı boyutu kullan
if (settings.showCompanyName) {
doc.setFontSize(settings.normalFontSize + 2);
doc.setFont(undefined, settings.fontWeight);
doc.text(fixTurkishChars(companyData.name), settings.showCompanyLogo ? 45 : 15, settings.logoTopMargin + 8);
}
// Firma bilgileri - sayfa tipine göre görünürlük ve yazı boyutu kullan
doc.setFontSize(settings.smallFontSize);
doc.setFont(undefined, 'normal');
let infoY = settings.logoTopMargin + 8 + pdfSettings.tableRowHeight;
// Adres
if (settings.showCompanyAddress && companyData.address) {
doc.text(fixTurkishChars(companyData.address), 15, infoY);
infoY += pdfSettings.tableRowHeight;
}
// İletişim bilgileri
let contactInfo = [];
if (settings.showCompanyPhone && companyData.phone) {
contactInfo.push(fixTurkishChars(`Telefon: ${companyData.phone}`));
}
if (settings.showCompanyEmail && companyData.email) {
contactInfo.push(fixTurkishChars(`E-posta: ${companyData.email}`));
}
if (settings.showCompanyWebsite && companyData.website) {
contactInfo.push(fixTurkishChars(`Web: ${companyData.website}`));
}
if (contactInfo.length > 0) {
doc.text(contactInfo.join(' | '), 15, infoY);
infoY += pdfSettings.tableRowHeight;
}
// Firma açıklaması
if (settings.showCompanyDescription && companyData.description) {
doc.text(fixTurkishChars(companyData.description), 15, infoY);
infoY += pdfSettings.tableRowHeight;
}
// Sayfa numarası - sayfa tipine göre yazı boyutu kullan
doc.setFontSize(settings.smallFontSize);
doc.text(`Sayfa ${pageNumber}/${totalPages}`, 190, settings.logoTopMargin + 8, { align: 'right' });
// Ayırıcı çizgi - sayfa tipine göre kalınlık ve pozisyon kullan
const separatorY = settings.separatorTopMargin;
const lineWidth = settings.separatorThickness;
const pageWidth = 210;
const leftMargin = 15;
const rightMargin = 195;
let lineStartX = leftMargin;
let lineEndX = rightMargin;
switch(settings.separatorPosition) {
case 'left':
lineEndX = leftMargin + (pageWidth - 40) * 0.3;
break;
case 'center':
lineStartX = leftMargin + (pageWidth - 40) * 0.35;
lineEndX = leftMargin + (pageWidth - 40) * 0.65;
break;
case 'right':
lineStartX = leftMargin + (pageWidth - 40) * 0.7;
break;
case 'full':
lineStartX = leftMargin;
lineEndX = rightMargin;
break;
}
doc.setLineWidth(lineWidth);
doc.line(lineStartX, separatorY, lineEndX, separatorY);
return settings.separatorTopMargin + pdfSettings.tableRowHeight * 2; // İçerik başlangıç y pozisyonu
}
const editCustomer = (customerName) => {
const customer = customerData[customerName];
openCustomerEditModal(customerName, customer);
};
// Cari düzenleme modalını açma
const openCustomerEditModal = (customerName, customerData) => {
$('editCustomerName').value = customerName;
$('editCustomerPhone').value = customerData.info.phone || '';
$('editCustomerEmail').value = customerData.info.email || '';
$('editCustomerAddress').value = customerData.info.address || '';
$('editCustomerCreatedDate').value = new Date(customerData.info.createdAt).toLocaleDateString('tr-TR');
$('editCustomerPosCount').value = Object.keys(customerData.pos).length;
$('customerEditModal').style.display = 'block';
};
// Cari düzenleme modalını kapatma
const closeCustomerEditModal = () => {
$('customerEditModal').style.display = 'none';
};
// Cari bilgilerini güncelleme
const saveCustomerEdit = () => {
const oldName = $('editCustomerName').value.trim();
const newName = $('newEditCustomerName').value.trim();
const phone = $('editCustomerPhone').value.trim();
const email = $('editCustomerEmail').value.trim();
const address = $('editCustomerAddress').value.trim();
if (!newName) return alert('Cari adı gerekli!');
// Eğer isim değişmişse ve yeni isim başka bir cari ile çakışıyorsa
if (newName !== oldName && customerData[newName]) {
alert('Bu isimde bir cari zaten var!');
return;
}
const customer = customerData[oldName];
if (newName !== oldName) {
// İsim değişmişse yeni isimle kaydet
customerData[newName] = {
...customer,
info: {
...customer.info,
phone,
email,
address
}
};
delete customerData[oldName];
} else {
// İsim aynıysa sadece bilgileri güncelle
customerData[oldName] = {
...customer,
info: {
...customer.info,
phone,
email,
address
}
};
}
if (saveCustomerData()) {
updateCustomerList();
renderCustomerList();
updateNewCustomerButton();
closeCustomerEditModal();
// Eğer seçili cari düzenlendiyse, seçimi güncelle
if (selectedCustomer === oldName) {
selectedCustomer = newName;
loadCustomerData();
}
alert('Cari başarıyla güncellendi!');
}
};
const deleteCustomer = (customerName) => {
const customer = customerData[customerName];
const posCount = Object.keys(customer.pos).length;
// Eğer cari içinde poz varsa silmeyi engelle
if (posCount > 0) {
alert(`"${customerName}" cariyi silemezsiniz çünkü ${posCount} adet poz içeriyor.\n\nLütfen önce pozları başka bir cariye transfer edin veya pozları silin.`);
return;
}
if (!confirm(`"${customerName}" cariyi silmek istediğinizden emin misiniz?`)) return;
delete customerData[customerName];
if (saveCustomerData()) {
updateCustomerList();
renderCustomerList();
updateNewCustomerButton();
if (selectedCustomer === customerName) {
selectedCustomer = null;
$('customerPosContainer').innerHTML = '<div class="empty-state">Cari seçmek için listeden bir cariye tıklayın</div>';
}
alert('Cari başarıyla silindi!');
}
};
const generateCustomerPosPDF = (customerName, pozNumber) => {
const customer = customerData[customerName];
const pos = customer.pos[pozNumber];
generateCalculationPDF(pos);
};
const deleteCustomerPos = (customerName, pozNumber) => {
if (!confirm('Bu poz cariden silinecek. Emin misiniz?')) return;
const customer = customerData[customerName];
delete customer.pos[pozNumber];
if (saveCustomerData()) {
loadCustomerData();
}
};
</script>
<script src="https://huggingface.co/deepsite/deepsite-badge.js">
</script>
<!-- Enhanced IndexedDB Veritabanı İşlemleri -->
<script>
// IndexedDB Veritabanı Yapısı
const DB_NAME = 'PencereHesaplamaDB';
const DB_VERSION = 2; // Version increased for new features
let db = null;
let isDatabaseReady = false;
// Veritabanı tablo yapıları
const TABLES = {
systems: 'pencere_sistemleri',
profiles: 'sistem_profilleri',
customers: 'cariler',
positions: 'pozlar',
company: 'firma_bilgileri',
settings: 'pdf_ayarlar',
backups: 'veri_yedekleri',
logs: 'islem_loglari'
};
// Veritabanı durum takibi
const DB_STATUS = {
NOT_INITIALIZED: 'not_initialized',
INITIALIZING: 'initializing',
READY: 'ready',
ERROR: 'error',
MIGRATING: 'migrating'
};
// Veritabanı durumunu güncelleme
function updateDatabaseStatus(status, message = '') {
const statusContent = document.getElementById('dbStatusContent');
if (!statusContent) return;
const statusConfig = {
'not_initialized': { color: '#718096', icon: '🔄', text: 'Başlatılıyor...' },
'initializing': { color: '#3182ce', icon: '⚡', text: 'Kuruluyor...' },
'ready': { color: '#38a169', icon: '✅', text: 'Hazır' },
'error': { color: '#e53e3e', icon: '❌', text: 'Hata' },
'migrating': { color: '#d69e2e', icon: '🔄', text: 'Taşınıyor...' }
};
const config = statusConfig[status] || statusConfig['not_initialized'];
statusContent.innerHTML = `
<h4 style="color: ${config.color}; margin-bottom: 10px;">
${config.icon} Veritabanı Durumu: ${config.text}
</h4>
${message ? `<div style="margin: 10px 0; padding: 10px; background: #f8fafc; border-radius: 6px; font-size: 14px;">${message}</div>` : ''}
<div class="database-status-actions">
<button onclick="quickSetupDatabase()" class="add-btn">
🚀 Hızlı Kurulum
</button>
<button onclick="checkDatabaseHealth()" class="edit-btn">
🔍 Sağlık Kontrolü
</button>
<button onclick="resetDatabase()" class="delete-btn">
🔄 Sıfırla
</button>
</div>
`;
}
// Veritabanını aç
async function openDatabase() {
if (isDatabaseReady && db) return db;
updateDatabaseStatus(DB_STATUS.INITIALIZING, 'Veritabanı bağlantısı kuruluyor...');
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => {
updateDatabaseStatus(DB_STATUS.ERROR, `Bağlantı hatası: ${request.error?.message || 'Bilinmeyen hata'}`);
reject(request.error);
};
request.onsuccess = () => {
db = request.result;
isDatabaseReady = true;
// Veritabanı olay dinleyicileri
db.onerror = (event) => {
console.error('Veritabanı hatası:', event.target.error);
logDatabaseError(event.target.error);
};
updateDatabaseStatus(DB_STATUS.READY, `Veritabanı başarıyla bağlandı. Tablolar: ${Array.from(db.objectStoreNames).join(', ')}`);
resolve(db);
};
request.onupgradeneeded = (event) => {
db = event.target.result;
updateDatabaseStatus(DB_STATUS.MIGRATING, 'Veritabanı şeması güncelleniyor...');
// Hata işleme
db.onerror = (event) => {
console.error('Veritabanı yükseltme hatası:', event.target.error);
};
// Sistemler tablosu
if (!db.objectStoreNames.contains(TABLES.systems)) {
const systemsStore = db.createObjectStore(TABLES.systems, { keyPath: 'id', autoIncrement: true });
systemsStore.createIndex('name', 'name', { unique: false });
systemsStore.createIndex('created_at', 'created_at', { unique: false });
systemsStore.createIndex('updated_at', 'updated_at', { unique: false });
}
// Profiller tablosu
if (!db.objectStoreNames.contains(TABLES.profiles)) {
const profilesStore = db.createObjectStore(TABLES.profiles, { keyPath: 'id', autoIncrement: true });
profilesStore.createIndex('sistem_id', 'sistem_id', { unique: false });
profilesStore.createIndex('type', 'type', { unique: false });
profilesStore.createIndex('created_at', 'created_at', { unique: false });
profilesStore.createIndex('updated_at', 'updated_at', { unique: false });
}
// Cariler tablosu
if (!db.objectStoreNames.contains(TABLES.customers)) {
const customersStore = db.createObjectStore(TABLES.customers, { keyPath: 'id', autoIncrement: true });
customersStore.createIndex('name', 'name', { unique: true });
customersStore.createIndex('phone', 'phone', { unique: false });
customersStore.createIndex('email', 'email', { unique: false });
customersStore.createIndex('created_at', 'created_at', { unique: false });
customersStore.createIndex('updated_at', 'updated_at', { unique: false });
}
// Pozlar tablosu
if (!db.objectStoreNames.contains(TABLES.positions)) {
const positionsStore = db.createObjectStore(TABLES.positions, { keyPath: 'id', autoIncrement: true });
positionsStore.createIndex('cari_id', 'cari_id', { unique: false });
positionsStore.createIndex('sistem_id', 'sistem_id', { unique: false });
positionsStore.createIndex('project_name', 'project_name', { unique: false });
positionsStore.createIndex('created_at', 'created_at', { unique: false });
positionsStore.createIndex('updated_at', 'updated_at', { unique: false });
}
// Firma bilgileri tablosu
if (!db.objectStoreNames.contains(TABLES.company)) {
const companyStore = db.createObjectStore(TABLES.company, { keyPath: 'id', autoIncrement: true });
companyStore.createIndex('name', 'name', { unique: false });
companyStore.createIndex('created_at', 'created_at', { unique: false });
companyStore.createIndex('updated_at', 'updated_at', { unique: false });
}
// PDF ayarları tablosu
if (!db.objectStoreNames.contains(TABLES.settings)) {
const settingsStore = db.createObjectStore(TABLES.settings, { keyPath: 'id', autoIncrement: true });
settingsStore.createIndex('type', 'type', { unique: false });
settingsStore.createIndex('created_at', 'created_at', { unique: false });
settingsStore.createIndex('updated_at', 'updated_at', { unique: false });
}
// Yedekler tablosu (yeni)
if (!db.objectStoreNames.contains(TABLES.backups)) {
const backupsStore = db.createObjectStore(TABLES.backups, { keyPath: 'id', autoIncrement: true });
backupsStore.createIndex('backup_type', 'backup_type', { unique: false });
backupsStore.createIndex('created_at', 'created_at', { unique: false });
backupsStore.createIndex('file_size', 'file_size', { unique: false });
}
// İşlem logları tablosu (yeni)
if (!db.objectStoreNames.contains(TABLES.logs)) {
const logsStore = db.createObjectStore(TABLES.logs, { keyPath: 'id', autoIncrement: true });
logsStore.createIndex('operation', 'operation', { unique: false });
logsStore.createIndex('table_name', 'table_name', { unique: false });
logsStore.createIndex('created_at', 'created_at', { unique: false });
logsStore.createIndex('user_id', 'user_id', { unique: false });
}
};
});
}
// Hızlı veritabanı kurulumu
async function quickSetupDatabase() {
updateDatabaseStatus(DB_STATUS.INITIALIZING, 'Hızlı kurulum başlatılıyor...');
try {
// Adım 1: Veritabanını aç
updateProgress(1, 8, 'Veritabanı bağlantısı kuruluyor...');
await openDatabase();
// Adım 2: Temel verileri kontrol et
updateProgress(2, 8, 'Temel veriler kontrol ediliyor...');
await ensureBasicData();
// Adım 3: Veri bütünlüğünü kontrol et
updateProgress(3, 8, 'Veri bütünlüğü kontrol ediliyor...');
await validateDataIntegrity();
// Adım 4: İndeksleri oluştur
updateProgress(4, 8, 'İndeksler oluşturuluyor...');
await createOptimizationIndexes();
// Adım 5: localStorage'dan veri taşı (varsa)
updateProgress(5, 8, 'Mevcut veriler taşınıyor...');
await migrateDataFromLocalStorage();
// Adım 6: Sistem verilerini taşı
updateProgress(6, 8, 'Sistem verileri taşınıyor...');
await migrateSystems();
// Adım 7: Cari verilerini taşı
updateProgress(7, 8, 'Cari verileri taşınıyor...');
await migrateCustomers();
// Adım 8: Poz verilerini taşı
updateProgress(8, 8, 'Poz verileri taşınıyor...');
await migratePositions();
// Başarılı completion
const tableCount = Array.from(db.objectStoreNames).length;
updateDatabaseStatus(DB_STATUS.READY, `
<div style="margin-top: 15px; padding: 15px; background: #f0fff4; border-radius: 8px; border: 1px solid #38a169;">
<strong style="color: #38a169;">🎉 Veritabanı Kurulumu Tamamlandı!</strong><br>
<div style="margin-top: 10px; font-size: 13px;">
${tableCount} adet tablo oluşturuldu<br>
• Tüm veriler başarıyla taşındı<br>
• Performans indeksleri oluşturuldu<br>
• Veri bütünlüğü doğrulandı<br>
• Veritabanı kullanıma hazır ✨
</div>
<div style="margin-top: 15px;">
<button class="add-btn" onclick="showDatabaseDashboard()" style="padding: 8px 16px; font-size: 12px;">
📊 Veritabanı Paneli
</button>
</div>
</div>
`);
// Global değişkenleri ayarla
window.useIndexedDB = true;
// Log kaydı
await logDatabaseOperation('DATABASE_SETUP', 'system', 'Veritabanı hızlı kurulumu tamamlandı');
// Otomatik panel gizleme
setTimeout(() => {
const setupSection = document.getElementById('databaseSetupSection');
if (setupSection) {
setupSection.style.transition = 'opacity 0.5s';
setupSection.style.opacity = '0.7';
}
}, 5000);
} catch (error) {
console.error('Hızlı kurulum hatası:', error);
updateDatabaseStatus(DB_STATUS.ERROR, `
<div style="margin-top: 15px; padding: 15px; background: #fff5f5; border-radius: 8px; border: 1px solid #e53e3e;">
<strong style="color: #e53e3e;">❌ Kurulum Hatası</strong><br>
<div style="margin-top: 10px; font-size: 13px;">
<strong>Hata:</strong> ${error.message}<br>
<strong>Kod:</strong> ${error.code || 'Bilinmeyen'}<br>
</div>
<div style="margin-top: 15px;">
<button class="edit-btn" onclick="retrySetup()" style="padding: 8px 16px; font-size: 12px;">
🔄 Tekrar Dene
</button>
<button class="delete-btn" onclick="resetDatabase()" style="padding: 8px 16px; font-size: 12px;">
🗑️ Temizle
</button>
</div>
</div>
`);
await logDatabaseOperation('DATABASE_ERROR', 'system', `Kurulum hatası: ${error.message}`);
}
}
// Gelişmiş kurulum fonksiyonu (orijinal)
async function setupDatabase() {
return await quickSetupDatabase();
}
// İlerleme güncelleme
function updateProgress(current, total, message) {
const percentage = Math.round((current / total) * 100);
const statusContent = document.getElementById('dbStatusContent');
if (statusContent) {
statusContent.innerHTML = `
<h4 style="color: #2b6cb0; margin-bottom: 10px;">⚡ Veritabanı Kuruluyor...</h4>
<div class="database-progress">
<div>${current}/${total} - ${message}</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${percentage}%; background: linear-gradient(90deg, #4a90e2, #667eea);"></div>
</div>
<div style="font-size: 12px; color: #718096; margin-top: 5px;">%${percentage} tamamlandı</div>
</div>
`;
}
}
// Temel verileri ensure et
async function ensureBasicData() {
if (!db) throw new Error('Veritabanı bağlantısı yok');
const transaction = db.transaction([TABLES.settings], 'readwrite');
const settingsStore = transaction.objectStore(TABLES.settings);
// Temel ayarları kontrol et
const hasSettings = await new Promise((resolve) => {
const request = settingsStore.count();
request.onsuccess = () => resolve(request.result > 0);
});
if (!hasSettings) {
// Varsayılan ayarları ekle
await new Promise((resolve) => {
settingsStore.add({
type: 'default',
settings_json: JSON.stringify({
version: DB_VERSION,
theme: 'light',
language: 'tr',
auto_backup: true,
backup_interval: 7 // gün
}),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
});
});
}
}
// Veri bütünlüğü doğrulama
async function validateDataIntegrity() {
if (!db) throw new Error('Veritabanı bağlantısı yok');
const tables = [TABLES.systems, TABLES.customers, TABLES.positions];
const results = {};
for (const tableName of tables) {
const transaction = db.transaction([tableName], 'readonly');
const store = transaction.objectStore(tableName);
results[tableName] = await new Promise((resolve) => {
const request = store.count();
request.onsuccess = () => resolve(request.result);
});
}
return results;
}
// Optimizasyon indeksleri oluştur
async function createOptimizationIndexes() {
if (!db) return;
// Bu fonksiyon gelecekteki optimizasyonlar için placeholder
// IndexedDB versiyon upgrade sırasında indeksler zaten oluşturuluyor
return true;
}
// Kurulumu yeniden dene
async function retrySetup() {
updateDatabaseStatus(DB_STATUS.INITIALIZING, 'Kurulum yeniden deneniyor...');
await quickSetupDatabase();
}
// localStorage'dan veri taşıma
async function migrateDataFromLocalStorage() {
// Bu fonksiyon localStorage'daki mevcut verileri IndexedDB'ye taşır
return new Promise((resolve) => {
resolve();
});
}
// Sistemleri taşı
async function migrateSystems() {
if (!systems || systems.length === 0) return;
const transaction = db.transaction([TABLES.systems, TABLES.profiles], 'readwrite');
const systemsStore = transaction.objectStore(TABLES.systems);
const profilesStore = transaction.objectStore(TABLES.profiles);
for (const system of systems) {
// Sistemi ekle
const systemRequest = systemsStore.add({
name: system.name,
image: system.image,
created_at: new Date().toISOString()
});
systemRequest.onsuccess = (event) => {
const systemId = event.target.result;
// Profilleri ekle
if (system.parts && system.parts.length > 0) {
system.parts.forEach(part => {
profilesStore.add({
sistem_id: systemId,
name: part.name,
type: part.type,
quantity: part.quantity,
reduction: part.reduction,
description: part.description,
image: part.image,
formula: JSON.stringify(part.advancedFormula || part.glassFormulas || {}),
created_at: new Date().toISOString()
});
});
}
};
}
return new Promise((resolve) => {
transaction.oncomplete = () => resolve();
});
}
// Carileri taşı
async function migrateCustomers() {
if (!customerData || Object.keys(customerData).length === 0) return;
const transaction = db.transaction([TABLES.customers, TABLES.positions], 'readwrite');
const customersStore = transaction.objectStore(TABLES.customers);
const positionsStore = transaction.objectStore(TABLES.positions);
for (const [customerName, customerInfo] of Object.entries(customerData)) {
// Carileri ekle
const customerRequest = customersStore.add({
name: customerName,
phone: customerInfo.info?.phone || '',
email: customerInfo.info?.email || '',
address: customerInfo.info?.address || '',
created_at: customerInfo.info?.createdAt || new Date().toISOString()
});
customerRequest.onsuccess = (event) => {
const customerId = event.target.result;
// Pozları ekle
if (customerInfo.pos && Object.keys(customerInfo.pos).length > 0) {
Object.values(customerInfo.pos).forEach(pos => {
positionsStore.add({
cari_id: customerId,
sistem_id: pos.system?.id || null,
project_name: pos.projectName,
width: pos.width,
height: pos.height,
quantity: pos.quantity,
horizontal_parts: JSON.stringify(pos.horizontalParts || []),
vertical_parts: JSON.stringify(pos.verticalParts || []),
glass_parts: JSON.stringify(pos.glassParts || []),
glass_info: JSON.stringify(pos.glassInfo || {}),
created_at: pos.timestamp || new Date().toISOString()
});
});
}
};
}
return new Promise((resolve) => {
transaction.oncomplete = () => resolve();
});
}
// Pozları taşı
async function migratePositions() {
if (!posList || posList.length === 0) return;
const transaction = db.transaction(TABLES.positions, 'readwrite');
const positionsStore = transaction.objectStore(TABLES.positions);
posList.forEach(pos => {
positionsStore.add({
cari_id: null, // Genel pozlar
sistem_id: pos.system?.id || null,
project_name: pos.projectName,
width: pos.width,
height: pos.height,
quantity: pos.quantity,
horizontal_parts: JSON.stringify(pos.horizontalParts || []),
vertical_parts: JSON.stringify(pos.verticalParts || []),
glass_parts: JSON.stringify(pos.glassParts || []),
glass_info: JSON.stringify(pos.glassInfo || {}),
created_at: pos.timestamp || new Date().toISOString()
});
});
return new Promise((resolve) => {
transaction.oncomplete = () => resolve();
});
}
// Firma bilgilerini ve ayarları taşı
async function migrateCompanyAndSettings() {
const transaction = db.transaction([TABLES.company, TABLES.settings], 'readwrite');
const companyStore = transaction.objectStore(TABLES.company);
const settingsStore = transaction.objectStore(TABLES.settings);
// Firma bilgilerini ekle
if (companyData) {
companyStore.add({
name: companyData.name,
logo: companyData.logo,
address: companyData.address,
phone: companyData.phone,
email: companyData.email,
website: companyData.website,
description: companyData.description,
created_at: companyData.createdAt || new Date().toISOString()
});
}
// PDF ayarlarını ekle
if (pdfSettings) {
settingsStore.add({
type: 'pdf',
settings_json: JSON.stringify(pdfSettings),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
});
}
// Sayfa bazlı ayarları ekle
if (perPageSettings) {
settingsStore.add({
type: 'per_page',
settings_json: JSON.stringify(perPageSettings),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
});
}
return new Promise((resolve) => {
transaction.oncomplete = () => resolve();
});
}
// Veritabanı sağlık kontrolü
async function checkDatabaseHealth() {
updateDatabaseStatus(DB_STATUS.INITIALIZING, 'Sağlık kontrolü yapılıyor...');
try {
const health = await performDatabaseHealthCheck();
displayHealthResults(health);
} catch (error) {
updateDatabaseStatus(DB_STATUS.ERROR, `Sağlık kontrolü hatası: ${error.message}`);
}
}
// Veritabanı durumunu kontrol et (gelişmiş)
async function checkDatabaseStatus() {
const statusContent = document.getElementById('dbStatusContent');
try {
await openDatabase();
if (!db) {
throw new Error('Veritabanı bağlantısı kurulamadı');
}
const tableNames = Array.from(db.objectStoreNames);
const expectedTables = Object.values(TABLES);
const missingTables = expectedTables.filter(table => !tableNames.includes(table));
if (missingTables.length === 0) {
// Tablolar var, detaylı kontrol yap
const healthCheck = await performDatabaseHealthCheck();
displayHealthResults(healthCheck);
} else {
statusContent.innerHTML = `
<h4 style="color: #d69e2e; margin-bottom: 10px;">⚠️ Eksik Tablolar</h4>
<div style="margin-top: 15px; padding: 10px; background: #fffaf0; border-radius: 6px; border: 1px solid #d69e2e;">
<strong>Eksik tablolar (${missingTables.length}):</strong><br>
${missingTables.map(table => `• ${table}`).join('<br>')}
<br><br>
<button class="add-btn" onclick="quickSetupDatabase()">🚀 Tamamla</button>
<button class="edit-btn" onclick="createMissingTables()">⚙️ Sadece Eksikleri Oluştur</button>
</div>
`;
}
} catch (error) {
statusContent.innerHTML = `
<h4 style="color: #e53e3e; margin-bottom: 10px;">❌ Veritabanı Hatası</h4>
<div style="margin-top: 15px; padding: 10px; background: #fff5f5; border-radius: 6px; border: 1px solid #e53e3e;">
<strong>Hata:</strong> ${error.message}<br>
<button class="add-btn" onclick="quickSetupDatabase()">🔄 Yeniden Kur</button>
</div>
`;
}
}
// Veritabanı sağlık kontrolü yap
async function performDatabaseHealthCheck() {
const tableNames = Array.from(db.objectStoreNames);
const health = {
status: 'healthy',
tables: {},
totalSize: 0,
performance: {},
issues: []
};
for (const tableName of tableNames) {
try {
const transaction = db.transaction([tableName], 'readonly');
const store = transaction.objectStore(tableName);
const count = await new Promise((resolve) => {
const request = store.count();
request.onsuccess = () => resolve(request.result);
});
health.tables[tableName] = { count };
health.totalSize += count;
// Performans testi
const startTime = performance.now();
await new Promise((resolve) => {
const request = store.openCursor();
request.onsuccess = (event) => {
if (event.target.result) {
event.target.result.continue();
} else {
resolve();
}
};
});
const endTime = performance.now();
health.performance[tableName] = {
queryTime: Math.round(endTime - startTime),
status: endTime - startTime < 100 ? 'good' : 'slow'
};
} catch (error) {
health.tables[tableName] = { error: error.message };
health.issues.push(`${tableName}: ${error.message}`);
}
}
// Genel durum belirle
if (health.issues.length > 0) {
health.status = 'warning';
}
// Depolama durumu
if ('storage' in navigator && 'estimate' in navigator.storage) {
try {
const estimate = await navigator.storage.estimate();
health.storage = {
quota: estimate.quota,
usage: estimate.usage,
percentage: Math.round((estimate.usage / estimate.quota) * 100)
};
} catch (error) {
health.storage = { error: error.message };
}
}
return health;
}
// Sağlık sonuçlarını göster
function displayHealthResults(health) {
const statusContent = document.getElementById('dbStatusContent');
const statusColors = {
healthy: '#38a169',
warning: '#d69e2e',
error: '#e53e3e'
};
const statusIcons = {
healthy: '✅',
warning: '⚠️',
error: '❌'
};
let html = `
<h4 style="color: ${statusColors[health.status]}; margin-bottom: 15px;">
${statusIcons[health.status]} Veritabanı Durumu: ${health.status.toUpperCase()}
</h4>
<div class="health-summary" style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px;">
<div style="padding: 15px; background: #f8fafc; border-radius: 8px; border-left: 4px solid #4a90e2;">
<div style="font-weight: bold; color: #2d3748;">📊 Tablolar</div>
<div style="font-size: 24px; font-weight: bold; color: #4a90e2;">${Object.keys(health.tables).length}</div>
<div style="font-size: 12px; color: #718096;">Toplam kayıt: ${health.totalSize}</div>
</div>
<div style="padding: 15px; background: #f8fafc; border-radius: 8px; border-left: 4px solid #38a169;">
<div style="font-weight: bold; color: #2d3748;">⚡ Performans</div>
<div style="font-size: 24px; font-weight: bold; color: #38a169;">
${Object.values(health.performance).filter(p => p.status === 'good').length}
</div>
<div style="font-size: 12px; color: #718096;">Hızlı tablo sayısı</div>
</div>
</div>
<div class="table-details" style="margin-bottom: 20px;">
<h5 style="margin-bottom: 10px; color: #4a5568;">Tablo Detayları:</h5>
`;
for (const [tableName, info] of Object.entries(health.tables)) {
const performance = health.performance[tableName];
const perfColor = performance?.status === 'good' ? '#38a169' : '#e53e3e';
html += `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; margin: 4px 0; background: #f8fafc; border-radius: 6px; border-left: 3px solid #4a90e2;">
<div>
<strong>${tableName}</strong>
${info.error ? `<span style="color: #e53e3e; font-size: 12px;"> - HATA</span>` : ''}
</div>
<div style="display: flex; gap: 15px; align-items: center;">
<span style="font-size: 14px;">${info.count || 0} kayıt</span>
${performance ? `<span style="font-size: 12px; color: ${perfColor};">⏱️ ${performance.queryTime}ms</span>` : ''}
</div>
</div>
`;
}
html += `
</div>
<div class="health-actions" style="margin-top: 20px;">
<button class="add-btn" onclick="optimizeDatabase()" style="padding: 8px 16px; font-size: 12px;">
🔧 Optimizasyon
</button>
<button class="edit-btn" onclick="createDatabaseBackup()" style="padding: 8px 16px; font-size: 12px;">
💾 Yedekle
</button>
<button class="catalog-btn" onclick="showDatabaseDashboard()" style="padding: 8px 16px; font-size: 12px;">
📊 Detaylı Panel
</button>
</div>
`;
// Depolama bilgisi
if (health.storage && !health.storage.error) {
html = `
<div style="margin-bottom: 15px; padding: 10px; background: #f0f7ff; border-radius: 6px; border: 1px solid #4a90e2;">
<strong>💾 Depolama:</strong>
${Math.round(health.storage.usage / 1024 / 1024)}MB / ${Math.round(health.storage.quota / 1024 / 1024)}MB
(${health.storage.percentage}%)
</div>
` + html;
}
// Sorunlar
if (health.issues.length > 0) {
html = `
<div style="margin-bottom: 15px; padding: 10px; background: #fffaf0; border-radius: 6px; border: 1px solid #d69e2e;">
<strong style="color: #d69e2e;">⚠️ Sorunlar:</strong><br>
${health.issues.map(issue => `• ${issue}`).join('<br>')}
</div>
` + html;
}
statusContent.innerHTML = html;
}
// Veritabanını sıfırla
async function resetDatabase() {
if (!confirm('Tüm veritabanı verileri silinecek. Bu işlem geri alınamaz! Emin misiniz?')) {
return;
}
try {
// IndexedDB'yi sil
const deleteRequest = indexedDB.deleteDatabase(DB_NAME);
deleteRequest.onsuccess = () => {
// localStorage'ı temizle
localStorage.clear();
// Global değişkenleri sıfırla
systems = [];
customerData = {};
posList = [];
companyData = {
name: 'Firma Adı',
logo: null,
address: '',
phone: '',
email: '',
website: '',
description: '',
createdAt: new Date().toISOString()
};
pdfSettings = {};
perPageSettings = {};
// UI'ı güncelle
updateSystemSelect();
updateCustomerList();
updatePosList();
updateStorageInfo();
alert('Veritabanı başarıyla sıfırlandı!');
// Durumu kontrol et
checkDatabaseStatus();
};
deleteRequest.onerror = () => {
throw new Error('Veritabanı silinemedi');
};
} catch (error) {
alert('Veritabanı sıfırlanırken hata oluştu: ' + error.message);
}
}
// Yeni yardımcı fonksiyonlar
// Eksik tabloları oluştur
async function createMissingTables() {
// Bu fonksiyon versiyon upgrade olmadan eksik tabloları oluşturur
updateDatabaseStatus(DB_STATUS.INITIALIZING, 'Eksik tablolar oluşturuluyor...');
// Not: IndexedDB'de tablolar sadece versiyon upgrade sırasında oluşturulabilir
// Bu nedenle veritabanı versiyonunu artırarak yeniden oluşturmamız gerekir
alert('Eksik tabloları oluşturmak için veritabanını yeniden kurmanız gerekmektedir. "Hızlı Kurulum" butonuna tıklayın.');
await quickSetupDatabase();
}
// Veritabanını optimize et
async function optimizeDatabase() {
updateDatabaseStatus(DB_STATUS.INITIALIZING, 'Veritabanı optimize ediliyor...');
try {
// Geçici verileri temizle
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const transaction = db.transaction([TABLES.logs], 'readwrite');
const logsStore = transaction.objectStore(TABLES.logs);
// Eski logları temizle
const deleteRequest = logsStore.index('created_at').openCursor(IDBKeyRange.upperBound(oneWeekAgo));
deleteRequest.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
await new Promise((resolve) => {
transaction.oncomplete = resolve;
});
updateDatabaseStatus(DB_STATUS.READY, 'Veritabanı optimize edildi. Eski loglar temizlendi.');
await logDatabaseOperation('DATABASE_OPTIMIZE', 'system', 'Veritabanı optimize edildi');
} catch (error) {
updateDatabaseStatus(DB_STATUS.ERROR, `Optimizasyon hatası: ${error.message}`);
}
}
// Veritabanı yedeği oluştur
async function createDatabaseBackup() {
updateDatabaseStatus(DB_STATUS.INITIALIZING, 'Yedek oluşturuluyor...');
try {
const backup = await exportAllDataFromDB();
const backupBlob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(backupBlob);
const link = document.createElement('a');
link.href = url;
link.download = `database-backup-${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
// Yedek kaydını veritabanına ekle
const transaction = db.transaction([TABLES.backups], 'readwrite');
const backupsStore = transaction.objectStore(TABLES.backups);
backupsStore.add({
backup_type: 'full',
file_size: backupBlob.size,
table_count: Object.keys(backup.data || {}).length,
created_at: new Date().toISOString()
});
updateDatabaseStatus(DB_STATUS.READY, 'Yedek başarıyla oluşturuldu ve indirildi.');
await logDatabaseOperation('BACKUP_CREATED', 'system', 'Veritabanı yedeği oluşturuldu');
} catch (error) {
updateDatabaseStatus(DB_STATUS.ERROR, `Yedekleme hatası: ${error.message}`);
}
}
// Veritabanı panelini göster
function showDatabaseDashboard() {
// Modal oluştur
const modal = document.createElement('div');
modal.className = 'modal';
modal.style.display = 'block';
modal.innerHTML = `
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2>📊 Veritabanı Paneli</h2>
<button class="close-btn" onclick="this.closest('.modal').remove()">&times;</button>
</div>
<div class="settings-section">
<div id="dashboardContent">
<div style="text-align: center; padding: 40px;">
<div class="loading-spinner"></div>
<p>Yükleniyor...</p>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// Panel içeriğini yükle
loadDashboardContent();
}
// Panel içeriğini yükle
async function loadDashboardContent() {
try {
const health = await performDatabaseHealthCheck();
const dashboardContent = document.getElementById('dashboardContent');
if (!dashboardContent) return;
dashboardContent.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px;">
<div class="dashboard-card" style="padding: 20px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; border-radius: 12px;">
<div style="font-size: 14px; opacity: 0.9;">Toplam Tablo</div>
<div style="font-size: 32px; font-weight: bold;">${Object.keys(health.tables).length}</div>
</div>
<div class="dashboard-card" style="padding: 20px; background: linear-gradient(135deg, #38a169, #48bb78); color: white; border-radius: 12px;">
<div style="font-size: 14px; opacity: 0.9;">Toplam Kayıt</div>
<div style="font-size: 32px; font-weight: bold;">${health.totalSize.toLocaleString()}</div>
</div>
<div class="dashboard-card" style="padding: 20px; background: linear-gradient(135deg, #d69e2e, #ed8936); color: white; border-radius: 12px;">
<div style="font-size: 14px; opacity: 0.9;">Performans</div>
<div style="font-size: 32px; font-weight: bold;">
${Object.values(health.performance).filter(p => p.status === 'good').length}
</div>
</div>
</div>
<h3 style="margin-bottom: 15px; color: #2d3748;">📈 Tablo Performansı</h3>
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f8fafc;">
<th style="padding: 12px; text-align: left; border-bottom: 1px solid #e2e8f0;">Tablo Adı</th>
<th style="padding: 12px; text-align: center; border-bottom: 1px solid #e2e8f0;">Kayıt Sayısı</th>
<th style="padding: 12px; text-align: center; border-bottom: 1px solid #e2e8f0;">Sorgu Süresi</th>
<th style="padding: 12px; text-align: center; border-bottom: 1px solid #e2e8f0;">Durum</th>
</tr>
</thead>
<tbody>
${Object.entries(health.tables).map(([tableName, info]) => {
const performance = health.performance[tableName];
const statusColor = performance?.status === 'good' ? '#38a169' : '#e53e3e';
const statusText = performance?.status === 'good' ? 'İyi' : 'Yavaş';
return `
<tr>
<td style="padding: 12px; border-bottom: 1px solid #e2e8f0;">
<strong>${tableName}</strong>
${info.error ? `<br><span style="color: #e53e3e; font-size: 12px;">${info.error}</span>` : ''}
</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid #e2e8f0;">
${info.count || 0}
</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid #e2e8f0;">
${performance ? `${performance.queryTime}ms` : '-'}
</td>
<td style="padding: 12px; text-align: center; border-bottom: 1px solid #e2e8f0;">
<span style="color: ${statusColor}; font-weight: bold;">
${statusText}
</span>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
} catch (error) {
const dashboardContent = document.getElementById('dashboardContent');
if (dashboardContent) {
dashboardContent.innerHTML = `
<div style="text-align: center; padding: 40px; color: #e53e3e;">
<h3>❌ Panel Yüklenemedi</h3>
<p>${error.message}</p>
</div>
`;
}
}
}
// Veritabanı loglama
async function logDatabaseError(error) {
if (!db) return;
try {
const transaction = db.transaction([TABLES.logs], 'readwrite');
const logsStore = transaction.objectStore(TABLES.logs);
logsStore.add({
operation: 'ERROR',
table_name: 'system',
error_message: error.message,
error_code: error.code,
created_at: new Date().toISOString(),
user_id: 'system'
});
} catch (logError) {
console.error('Log kaydı eklenemedi:', logError);
}
}
// Veritabanı işlem logu
async function logDatabaseOperation(operation, tableName, description = '') {
if (!db) return;
try {
const transaction = db.transaction([TABLES.logs], 'readwrite');
const logsStore = transaction.objectStore(TABLES.logs);
logsStore.add({
operation,
table_name: tableName,
description,
created_at: new Date().toISOString(),
user_id: 'system'
});
} catch (error) {
console.error('İşlem logu eklenemedi:', error);
}
}
// IndexedDB'den tüm verileri export et
async function exportAllDataFromDB() {
const exportData = {
metadata: {
version: DB_VERSION,
exportDate: new Date().toISOString(),
tables: Array.from(db.objectStoreNames)
},
data: {}
};
for (const tableName of db.objectStoreNames) {
const transaction = db.transaction([tableName], 'readonly');
const store = transaction.objectStore(tableName);
exportData.data[tableName] = await new Promise((resolve) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
});
}
return exportData;
}
// Sayfa yüklendiğinde otomatik kontrol
document.addEventListener('DOMContentLoaded', () => {
// Veritabanı durumunu kontrol et ama bekle
setTimeout(async () => {
try {
await checkDatabaseStatus();
} catch (error) {
console.error('Otomatik veritabanı kontrolü başarısız:', error);
}
}, 1000);
// Hızlı kurulum butonunu ekle (eğer section varsa)
const setupSection = document.getElementById('databaseSetupSection');
if (setupSection && !document.getElementById('quickSetupButton')) {
const quickSetupBtn = document.createElement('button');
quickSetupBtn.id = 'quickSetupButton';
quickSetupBtn.className = 'add-btn';
quickSetupBtn.innerHTML = '🚀 Hızlı Kurulum';
quickSetupBtn.style.cssText = 'margin: 10px 5px; padding: 12px 20px;';
quickSetupBtn.onclick = quickSetupDatabase;
setupSection.appendChild(quickSetupBtn);
}
});
</script>
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
</body>
</html>