Spaces:
Configuration error
Configuration error
| <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()">×</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()">×</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()">×</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()">×</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()">×</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()">×</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()">×</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()">×</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()">×</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()">×</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> | |