escambalkon commited on
Commit
7bfe715
·
verified ·
1 Parent(s): c185035

sunucuya veritabanı kur

Browse files
Files changed (6) hide show
  1. .env.example +14 -0
  2. api-client.js +261 -0
  3. index.html +722 -10
  4. login.html +238 -0
  5. package.json +28 -0
  6. server.js +617 -0
.env.example ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```
2
+ # Server Configuration
3
+ PORT=3001
4
+ NODE_ENV=development
5
+
6
+ # MongoDB Configuration
7
+ MONGODB_URI=mongodb://localhost:27017/pencere-hesaplama
8
+
9
+ # JWT Secret
10
+ JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
11
+
12
+ # Optional: MongoDB Atlas Cloud Database
13
+ # MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/pencere-hesaplama?retryWrites=true&w=majority
14
+ ```
api-client.js ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // API Client for Server Communication
2
+ class PencereAPI {
3
+ constructor() {
4
+ this.baseURL = process.env.API_BASE_URL || 'http://localhost:3001/api';
5
+ this.token = localStorage.getItem('authToken');
6
+ this.user = JSON.parse(localStorage.getItem('currentUser') || 'null');
7
+ }
8
+
9
+ // Set authentication token
10
+ setToken(token) {
11
+ this.token = token;
12
+ if (token) {
13
+ localStorage.setItem('authToken', token);
14
+ } else {
15
+ localStorage.removeItem('authToken');
16
+ }
17
+ }
18
+
19
+ // Set current user
20
+ setUser(user) {
21
+ this.user = user;
22
+ if (user) {
23
+ localStorage.setItem('currentUser', JSON.stringify(user));
24
+ } else {
25
+ localStorage.removeItem('currentUser');
26
+ }
27
+ }
28
+
29
+ // Generic request method
30
+ async request(endpoint, options = {}) {
31
+ const url = `${this.baseURL}${endpoint}`;
32
+ const config = {
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ ...options.headers
36
+ },
37
+ ...options
38
+ };
39
+
40
+ if (this.token) {
41
+ config.headers.Authorization = `Bearer ${this.token}`;
42
+ }
43
+
44
+ try {
45
+ const response = await fetch(url, config);
46
+
47
+ if (response.status === 401) {
48
+ // Token expired or invalid
49
+ this.setToken(null);
50
+ this.setUser(null);
51
+ window.location.href = '#login';
52
+ throw new Error('Authentication required');
53
+ }
54
+
55
+ const data = await response.json();
56
+
57
+ if (!response.ok) {
58
+ throw new Error(data.error || 'Request failed');
59
+ }
60
+
61
+ return data;
62
+ } catch (error) {
63
+ console.error(`API Error (${endpoint}):`, error);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ // Authentication methods
69
+ async login(username, password) {
70
+ const data = await this.request('/auth/login', {
71
+ method: 'POST',
72
+ body: JSON.stringify({ username, password })
73
+ });
74
+
75
+ this.setToken(data.token);
76
+ this.setUser(data.user);
77
+ return data;
78
+ }
79
+
80
+ async register(userData) {
81
+ return await this.request('/auth/register', {
82
+ method: 'POST',
83
+ body: JSON.stringify(userData)
84
+ });
85
+ }
86
+
87
+ logout() {
88
+ this.setToken(null);
89
+ this.setUser(null);
90
+ }
91
+
92
+ // Company methods
93
+ async getCompany() {
94
+ return await this.request('/company');
95
+ }
96
+
97
+ async updateCompany(companyData) {
98
+ return await this.request('/company', {
99
+ method: 'PUT',
100
+ body: companyData
101
+ });
102
+ }
103
+
104
+ // System methods
105
+ async getSystems() {
106
+ return await this.request('/systems');
107
+ }
108
+
109
+ async createSystem(systemData) {
110
+ return await this.request('/systems', {
111
+ method: 'POST',
112
+ body: systemData
113
+ });
114
+ }
115
+
116
+ async updateSystem(id, systemData) {
117
+ return await this.request(`/systems/${id}`, {
118
+ method: 'PUT',
119
+ body: systemData
120
+ });
121
+ }
122
+
123
+ async deleteSystem(id) {
124
+ return await this.request(`/systems/${id}`, {
125
+ method: 'DELETE'
126
+ });
127
+ }
128
+
129
+ // Customer methods
130
+ async getCustomers() {
131
+ return await this.request('/customers');
132
+ }
133
+
134
+ async createCustomer(customerData) {
135
+ return await this.request('/customers', {
136
+ method: 'POST',
137
+ body: customerData
138
+ });
139
+ }
140
+
141
+ async updateCustomer(id, customerData) {
142
+ return await this.request(`/customers/${id}`, {
143
+ method: 'PUT',
144
+ body: customerData
145
+ });
146
+ }
147
+
148
+ async deleteCustomer(id) {
149
+ return await this.request(`/customers/${id}`, {
150
+ method: 'DELETE'
151
+ });
152
+ }
153
+
154
+ // Position methods
155
+ async getPositions(customerId = null) {
156
+ const query = customerId ? `?customerId=${customerId}` : '';
157
+ return await this.request(`/positions${query}`);
158
+ }
159
+
160
+ async createPosition(positionData) {
161
+ return await this.request('/positions', {
162
+ method: 'POST',
163
+ body: positionData
164
+ });
165
+ }
166
+
167
+ async updatePosition(id, positionData) {
168
+ return await this.request(`/positions/${id}`, {
169
+ method: 'PUT',
170
+ body: positionData
171
+ });
172
+ }
173
+
174
+ async deletePosition(id) {
175
+ return await this.request(`/positions/${id}`, {
176
+ method: 'DELETE'
177
+ });
178
+ }
179
+
180
+ // PDF Settings methods
181
+ async getPDFSettings(type = null) {
182
+ const query = type ? `?type=${type}` : '';
183
+ return await this.request(`/pdf-settings${query}`);
184
+ }
185
+
186
+ async updatePDFSettings(settingsData, type = 'global') {
187
+ return await this.request('/pdf-settings', {
188
+ method: 'PUT',
189
+ body: { type, settings: settingsData }
190
+ });
191
+ }
192
+
193
+ // Backup methods
194
+ async createBackup() {
195
+ return await this.request('/backup');
196
+ }
197
+
198
+ async restoreData(backupData) {
199
+ return await this.request('/restore', {
200
+ method: 'POST',
201
+ body: backupData
202
+ });
203
+ }
204
+
205
+ // File upload helper
206
+ async uploadFile(file, endpoint = '/upload') {
207
+ const formData = new FormData();
208
+ formData.append('file', file);
209
+
210
+ const url = `${this.baseURL}${endpoint}`;
211
+ const config = {
212
+ method: 'POST',
213
+ body: formData,
214
+ headers: {}
215
+ };
216
+
217
+ if (this.token) {
218
+ config.headers.Authorization = `Bearer ${this.token}`;
219
+ }
220
+
221
+ try {
222
+ const response = await fetch(url, config);
223
+ const data = await response.json();
224
+
225
+ if (!response.ok) {
226
+ throw new Error(data.error || 'Upload failed');
227
+ }
228
+
229
+ return data;
230
+ } catch (error) {
231
+ console.error(`Upload Error (${endpoint}):`, error);
232
+ throw error;
233
+ }
234
+ }
235
+
236
+ // Check if user is authenticated
237
+ isAuthenticated() {
238
+ return !!this.token && !!this.user;
239
+ }
240
+
241
+ // Get user role
242
+ getUserRole() {
243
+ return this.user ? this.user.role : null;
244
+ }
245
+
246
+ // Check if user is admin
247
+ isAdmin() {
248
+ return this.getUserRole() === 'admin';
249
+ }
250
+ }
251
+
252
+ // Create global API instance
253
+ const api = new PencereAPI();
254
+
255
+ // Export for use in other modules
256
+ if (typeof module !== 'undefined' && module.exports) {
257
+ module.exports = PencereAPI;
258
+ } else {
259
+ window.PencereAPI = PencereAPI;
260
+ window.api = api;
261
+ }
index.html CHANGED
@@ -6,7 +6,8 @@
6
  <title>Pencere Ölçü Hesaplama - Profil Bazlı Cam Formülü</title>
7
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script>
9
- <style>
 
10
  /* CSS kodları aynı kalacak, değişiklik yapmıyorum */
11
  * { box-sizing: border-box; margin: 0; padding: 0; }
12
  body { font-family: 'Segoe UI', sans-serif; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
@@ -593,6 +594,13 @@ button { padding: 12px 20px; background: #4a90e2; color: white; border: none; bo
593
  <button onclick="showPage('mainPage')">Ana Sayfa</button>
594
  <button onclick="showPage('settingsPage')">Ayarlar</button>
595
  <button onclick="showPage('customerPage')">Cariler</button>
 
 
 
 
 
 
 
596
  </div>
597
  <div class="nav-logo-container">
598
  <img id="navCompanyLogo" src="" alt="Firma Logosu" class="nav-logo" style="display: none;">
@@ -635,10 +643,41 @@ button { padding: 12px 20px; background: #4a90e2; color: white; border: none; bo
635
  <div class="input-row">
636
  <input type="text" id="customerName" placeholder="Cari adı" list="customerList">
637
  <datalist id="customerList"></datalist>
638
- <div id="newCustomerButtonContainer"></div>
639
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  </div>
641
  </div>
 
 
 
642
  <button onclick="calculateAndAddPos()">Hesapla ve Poz Ekle</button>
643
 
644
  <!-- Poz Listesi Bölümü -->
@@ -1507,9 +1546,14 @@ button { padding: 12px 20px; background: #4a90e2; color: white; border: none; bo
1507
  <script>
1508
  // jsPDF'yi global olarak tanımla
1509
  window.jsPDF = window.jspdf.jsPDF;
1510
- let systems = JSON.parse(localStorage.getItem('pencereSystems')) || [];
1511
- let customerData = JSON.parse(localStorage.getItem('customerData')) || {};
1512
- let currentSystemId = null;
 
 
 
 
 
1513
  let currentPartId = null;
1514
  let calculationResults = {};
1515
  let isEditingSystem = false;
@@ -2251,23 +2295,691 @@ function savePDFSettingsData() {
2251
  updateStorageStatus();
2252
  }
2253
  };
2254
- document.addEventListener('DOMContentLoaded', () => {
 
 
 
 
 
 
 
 
 
 
 
 
2255
  updateSystemSelect();
2256
  updateCustomerList();
2257
  renderCustomerList();
2258
  updateNewCustomerButton();
2259
  updatePosList();
2260
  updateStorageInfo();
2261
- updateCompanyInfoDisplay(); // Firma bilgilerini güncelle
 
2262
 
2263
- // Test verisi ekle (eğer hiç veri yoksa)
2264
  if (systems.length === 0) {
2265
- addTestData();
 
 
2266
  }
2267
 
2268
  // Navbar logo'yu güncelle
2269
  updateNavbarLogo();
2270
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2271
  // GÜNCELLENMİŞ PDF OLUŞTURMA FONKSİYONU - MM ve ADET sütunları yer değiştirdi + Firma bilgileri eklendi
2272
  const generateCalculationPDF = (calc) => {
2273
  const { jsPDF } = window.jspdf;
 
6
  <title>Pencere Ölçü Hesaplama - Profil Bazlı Cam Formülü</title>
7
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script>
9
+ <script src="api-client.js"></script>
10
+ <style>
11
  /* CSS kodları aynı kalacak, değişiklik yapmıyorum */
12
  * { box-sizing: border-box; margin: 0; padding: 0; }
13
  body { font-family: 'Segoe UI', sans-serif; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
 
594
  <button onclick="showPage('mainPage')">Ana Sayfa</button>
595
  <button onclick="showPage('settingsPage')">Ayarlar</button>
596
  <button onclick="showPage('customerPage')">Cariler</button>
597
+ <button onclick="showPage('backupPage')">Yedekleme</button>
598
+ </div>
599
+ <div class="nav-center">
600
+ <span id="userInfo" class="user-info">Hoş geldiniz!</span>
601
+ </div>
602
+ <div class="nav-right">
603
+ <button onclick="logout()" class="logout-btn">Çıkış</button>
604
  </div>
605
  <div class="nav-logo-container">
606
  <img id="navCompanyLogo" src="" alt="Firma Logosu" class="nav-logo" style="display: none;">
 
643
  <div class="input-row">
644
  <input type="text" id="customerName" placeholder="Cari adı" list="customerList">
645
  <datalist id="customerList"></datalist>
646
+ <div id="newCustomerButtonContainer">
647
+ </div>
648
+ </div>
649
+
650
+ <!-- Yedekleme Sayfası -->
651
+ <div id="backupPage" style="display: none;">
652
+ <h1>Veri Yedekleme</h1>
653
+
654
+ <div class="settings-section">
655
+ <h2>🗄️ Veri Yedekleme</h2>
656
+ <p class="description">Tüm verilerinizi yedekleyebilir veya mevcut yedekleri geri yükleyebilirsiniz.</p>
657
+
658
+ <div class="backup-actions">
659
+ <button class="export-btn" onclick="createBackup()">
660
+ 💾 Veri Yedeği Oluştur
661
+ </button>
662
+ <button class="add-btn" onclick="document.getElementById('restoreFile').click()">
663
+ 📥 Yedekten Geri Yükle
664
+ </button>
665
+ <input type="file" id="restoreFile" accept=".json" style="display: none;" onchange="restoreBackup(event)">
666
+ </div>
667
+
668
+ <div id="backupStatus" class="status-message" style="display: none;"></div>
669
+ </div>
670
+
671
+ <div class="settings-section">
672
+ <h2>📊 Sunucu Durumu</h2>
673
+ <div id="serverStatus">
674
+ <div class="loading-spinner"></div>
675
+ <span>Sunucu durumu kontrol ediliyor...</span>
676
  </div>
677
  </div>
678
+ </div>
679
+ </div>
680
+ </div>
681
  <button onclick="calculateAndAddPos()">Hesapla ve Poz Ekle</button>
682
 
683
  <!-- Poz Listesi Bölümü -->
 
1546
  <script>
1547
  // jsPDF'yi global olarak tanımla
1548
  window.jsPDF = window.jspdf.jsPDF;
1549
+
1550
+ // Server API integration
1551
+ let systems = [];
1552
+ let customerData = {};
1553
+ let positions = [];
1554
+ let companyData = null;
1555
+ let pdfSettings = {};
1556
+ let currentSystemId = null;
1557
  let currentPartId = null;
1558
  let calculationResults = {};
1559
  let isEditingSystem = false;
 
2295
  updateStorageStatus();
2296
  }
2297
  };
2298
+ document.addEventListener('DOMContentLoaded', async () => {
2299
+ // Check authentication
2300
+ if (!api.isAuthenticated()) {
2301
+ window.location.href = 'login.html';
2302
+ return;
2303
+ }
2304
+
2305
+ // Update user info
2306
+ updateUserInfo();
2307
+
2308
+ // Load initial data
2309
+ await loadInitialData();
2310
+
2311
  updateSystemSelect();
2312
  updateCustomerList();
2313
  renderCustomerList();
2314
  updateNewCustomerButton();
2315
  updatePosList();
2316
  updateStorageInfo();
2317
+ updateCompanyInfoDisplay();
2318
+ updateServerStatus();
2319
 
2320
+ // Check for test data
2321
  if (systems.length === 0) {
2322
+ if (confirm('Sistem verisi bulunamadı. Test verisi oluşturulsun mu?')) {
2323
+ await addTestData();
2324
+ }
2325
  }
2326
 
2327
  // Navbar logo'yu güncelle
2328
  updateNavbarLogo();
2329
  });
2330
+
2331
+ // Update user information in navbar
2332
+ function updateUserInfo() {
2333
+ const userInfo = document.getElementById('userInfo');
2334
+ if (userInfo && api.user) {
2335
+ userInfo.textContent = `Hoş geldiniz, ${api.user.username}!`;
2336
+ }
2337
+ }
2338
+
2339
+ // Load initial data from server
2340
+ async function loadInitialData() {
2341
+ try {
2342
+ // Load systems
2343
+ systems = await api.getSystems();
2344
+
2345
+ // Load customers
2346
+ const customers = await api.getCustomers();
2347
+ customerData = {};
2348
+ customers.forEach(customer => {
2349
+ customerData[customer.name] = {
2350
+ info: {
2351
+ phone: customer.phone,
2352
+ email: customer.email,
2353
+ address: customer.address,
2354
+ createdAt: customer.createdAt
2355
+ },
2356
+ pos: {}
2357
+ };
2358
+ });
2359
+
2360
+ // Load positions
2361
+ positions = await api.getPositions();
2362
+
2363
+ // Load company data
2364
+ companyData = await api.getCompany();
2365
+
2366
+ // Load PDF settings
2367
+ const settingsData = await api.getPDFSettings();
2368
+ pdfSettings = settingsData.settings || {};
2369
+
2370
+ } catch (error) {
2371
+ console.error('Veri yükleme hatası:', error);
2372
+ showError('Veriler yüklenirken hata oluştu: ' + error.message);
2373
+ }
2374
+ }
2375
+
2376
+ // Logout function
2377
+ async function logout() {
2378
+ if (confirm('Çıkış yapmak istediğinizden emin misiniz?')) {
2379
+ api.logout();
2380
+ window.location.href = 'login.html';
2381
+ }
2382
+ }
2383
+
2384
+ // Show error message
2385
+ function showError(message) {
2386
+ const errorDiv = document.createElement('div');
2387
+ errorDiv.className = 'error-toast';
2388
+ errorDiv.innerHTML = `
2389
+ <div class="error-content">
2390
+ <span class="error-icon">⚠️</span>
2391
+ <span class="error-message">${message}</span>
2392
+ <button class="error-close" onclick="this.parentElement.parentElement.remove()">×</button>
2393
+ </div>
2394
+ `;
2395
+ document.body.appendChild(errorDiv);
2396
+
2397
+ setTimeout(() => {
2398
+ errorDiv.remove();
2399
+ }, 5000);
2400
+ }
2401
+
2402
+ // Show success message
2403
+ function showSuccess(message) {
2404
+ const successDiv = document.createElement('div');
2405
+ successDiv.className = 'success-toast';
2406
+ successDiv.innerHTML = `
2407
+ <div class="success-content">
2408
+ <span class="success-icon">✅</span>
2409
+ <span class="success-message">${message}</span>
2410
+ <button class="success-close" onclick="this.parentElement.parentElement.remove()">×</button>
2411
+ </div>
2412
+ `;
2413
+ document.body.appendChild(successDiv);
2414
+
2415
+ setTimeout(() => {
2416
+ successDiv.remove();
2417
+ }, 3000);
2418
+ }
2419
+
2420
+ // Update server status
2421
+ async function updateServerStatus() {
2422
+ const statusDiv = document.getElementById('serverStatus');
2423
+ if (!statusDiv) return;
2424
+
2425
+ try {
2426
+ const startTime = Date.now();
2427
+ await api.getSystems();
2428
+ const responseTime = Date.now() - startTime;
2429
+
2430
+ statusDiv.innerHTML = `
2431
+ <div class="server-status-success">
2432
+ <span class="status-icon">✅</span>
2433
+ <div>
2434
+ <strong>Sunucu Bağlantısı</strong><br>
2435
+ <small>Çevrimiçi - Yanıt süresi: ${responseTime}ms</small>
2436
+ </div>
2437
+ </div>
2438
+ `;
2439
+ } catch (error) {
2440
+ statusDiv.innerHTML = `
2441
+ <div class="server-status-error">
2442
+ <span class="status-icon">❌</span>
2443
+ <div>
2444
+ <strong>Sunucu Bağlantısı</strong><br>
2445
+ <small>Çevrimdışı: ${error.message}</small>
2446
+ </div>
2447
+ </div>
2448
+ `;
2449
+ }
2450
+ }
2451
+
2452
+ // Create backup
2453
+ async function createBackup() {
2454
+ const statusDiv = document.getElementById('backupStatus');
2455
+ statusDiv.style.display = 'block';
2456
+ statusDiv.className = 'status-message info';
2457
+ statusDiv.innerHTML = '<div class="loading-spinner"></div> Yedek oluşturuluyor...';
2458
+
2459
+ try {
2460
+ const backup = await api.createBackup();
2461
+
2462
+ // Create download link
2463
+ const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
2464
+ const url = URL.createObjectURL(blob);
2465
+ const a = document.createElement('a');
2466
+ a.href = url;
2467
+ a.download = `backup-${new Date().toISOString().split('T')[0]}.json`;
2468
+ a.click();
2469
+ URL.revokeObjectURL(url);
2470
+
2471
+ statusDiv.className = 'status-message success';
2472
+ statusDiv.innerHTML = '✅ Yedek başarı oluşturuldu ve indirildi.';
2473
+
2474
+ showSuccess('Yedekleme tamamlandı!');
2475
+ } catch (error) {
2476
+ statusDiv.className = 'status-message error';
2477
+ statusDiv.innerHTML = `❌ Yedekleme hatası: ${error.message}`;
2478
+ showError('Yedekleme başarısız: ' + error.message);
2479
+ }
2480
+ }
2481
+
2482
+ // Restore backup
2483
+ async function restoreBackup(event) {
2484
+ const file = event.target.files[0];
2485
+ if (!file) return;
2486
+
2487
+ const statusDiv = document.getElementById('backupStatus');
2488
+ statusDiv.style.display = 'block';
2489
+ statusDiv.className = 'status-message info';
2490
+ statusDiv.innerHTML = '<div class="loading-spinner"></div> Yedek geri yükleniyor...';
2491
+
2492
+ try {
2493
+ const text = await file.text();
2494
+ const backup = JSON.parse(text);
2495
+
2496
+ await api.restoreData(backup);
2497
+
2498
+ // Reload all data
2499
+ await loadInitialData();
2500
+ updateSystemSelect();
2501
+ updateCustomerList();
2502
+ renderCustomerList();
2503
+ updatePosList();
2504
+ updateCompanyInfoDisplay();
2505
+
2506
+ statusDiv.className = 'status-message success';
2507
+ statusDiv.innerHTML = '✅ Yedek başarıyla geri yüklendi.';
2508
+
2509
+ showSuccess('Yedek geri yükleme tamamlandı!');
2510
+
2511
+ // Clear file input
2512
+ event.target.value = '';
2513
+
2514
+ } catch (error) {
2515
+ statusDiv.className = 'status-message error';
2516
+ statusDiv.innerHTML = `❌ Geri yükleme hatası: ${error.message}`;
2517
+ showError('Yedek geri yükleme başarısız: ' + error.message);
2518
+ }
2519
+ }
2520
+
2521
+ // Override save functions to use API
2522
+ async function saveSystemData() {
2523
+ try {
2524
+ // Save systems that have been modified
2525
+ for (const system of systems) {
2526
+ if (system._id) {
2527
+ await api.updateSystem(system._id, system);
2528
+ } else {
2529
+ const newSystem = await api.createSystem(system);
2530
+ system._id = newSystem._id;
2531
+ }
2532
+ }
2533
+ return true;
2534
+ } catch (error) {
2535
+ console.error('Sistem kaydetme hatası:', error);
2536
+ showError('Sistem kaydedilemedi: ' + error.message);
2537
+ return false;
2538
+ }
2539
+ }
2540
+
2541
+ async function saveCustomerData() {
2542
+ try {
2543
+ // Convert customerData back to array format for API
2544
+ const customers = Object.entries(customerData).map(([name, data]) => ({
2545
+ name,
2546
+ phone: data.info.phone,
2547
+ email: data.info.email,
2548
+ address: data.info.address
2549
+ }));
2550
+
2551
+ for (const customer of customers) {
2552
+ if (customer._id) {
2553
+ await api.updateCustomer(customer._id, customer);
2554
+ } else {
2555
+ const newCustomer = await api.createCustomer(customer);
2556
+ customer._id = newCustomer._id;
2557
+ }
2558
+ }
2559
+ return true;
2560
+ } catch (error) {
2561
+ console.error('Cari kaydetme hatası:', error);
2562
+ showError('Cari kaydedilemedi: ' + error.message);
2563
+ return false;
2564
+ }
2565
+ }
2566
+
2567
+ async function savePosList() {
2568
+ try {
2569
+ for (const position of positions) {
2570
+ if (position._id) {
2571
+ await api.updatePosition(position._id, position);
2572
+ } else {
2573
+ const newPosition = await api.createPosition(position);
2574
+ position._id = newPosition._id;
2575
+ }
2576
+ }
2577
+ return true;
2578
+ } catch (error) {
2579
+ console.error('Poz kaydetme hatası:', error);
2580
+ showError('Poz kaydedilemedi: ' + error.message);
2581
+ return false;
2582
+ }
2583
+ }
2584
+
2585
+ // Override data loading functions
2586
+ async function updateSystemSelect() {
2587
+ const select = document.getElementById('systemSelect');
2588
+ if (select) {
2589
+ select.innerHTML = '<option value="">Sistem seçin...</option>' +
2590
+ systems.map(s => `<option value="${s._id || s.id}">${s.name}</option>`).join('');
2591
+ }
2592
+ }
2593
+
2594
+ async function updateCustomerList() {
2595
+ const customerList = document.getElementById('customerList');
2596
+ if (customerList) {
2597
+ customerList.innerHTML = '';
2598
+ Object.keys(customerData).forEach(customerName => {
2599
+ const option = document.createElement('option');
2600
+ option.value = customerName;
2601
+ customerList.appendChild(option);
2602
+ });
2603
+ }
2604
+ }
2605
+
2606
+ // Override position functions
2607
+ async function updatePosList() {
2608
+ const container = document.getElementById('posListContainer');
2609
+ const list = document.getElementById('posList');
2610
+
2611
+ if (!list) return;
2612
+
2613
+ if (positions.length === 0) {
2614
+ if (container) container.style.display = 'none';
2615
+ list.innerHTML = '<div class="empty-state">Henüz poz yok</div>';
2616
+ return;
2617
+ }
2618
+
2619
+ if (container) container.style.display = 'block';
2620
+
2621
+ let html = '';
2622
+ positions.forEach((pos, index) => {
2623
+ const camParts = pos.systemId?.parts?.filter(p => p.type === 'cam') || [];
2624
+ let camInfoHtml = '';
2625
+
2626
+ if (camParts.length > 0) {
2627
+ camParts.forEach(camPart => {
2628
+ let camWidth = pos.width - 20;
2629
+ let camHeight = pos.height - 20;
2630
+
2631
+ if (camPart.glassFormulas) {
2632
+ try {
2633
+ camWidth = evaluateFormula(camPart.glassFormulas.horizontal, pos.width, pos.height);
2634
+ camHeight = evaluateFormula(camPart.glassFormulas.vertical, pos.width, pos.height);
2635
+ } catch (error) {
2636
+ console.error('Cam formülü hatası:', error);
2637
+ }
2638
+ }
2639
+
2640
+ camInfoHtml += `<div>${camPart.description}: ${Math.round(camWidth)}x${Math.round(camHeight)}mm (${camPart.quantity * pos.quantity} adet)</div>`;
2641
+ });
2642
+ } else {
2643
+ camInfoHtml = `<div>${pos.glassInfo?.width || pos.width - 20}x${pos.glassInfo?.height || pos.height - 20}mm (${pos.quantity} adet)</div>`;
2644
+ }
2645
+
2646
+ html += `
2647
+ <div class="pos-item">
2648
+ <div class="pos-header">
2649
+ <div class="pos-title">Poz #${pos.pozNumber || (index + 1)} - ${pos.projectName || 'Projesiz'}</div>
2650
+ <div class="pos-actions">
2651
+ <button class="edit-btn" onclick="editPos(${index})">Düzenle</button>
2652
+ <button class="pdf-btn" onclick="generatePosPDF(${index})">PDF</button>
2653
+ <button class="delete-btn" onclick="deletePos(${index})">Sil</button>
2654
+ </div>
2655
+ </div>
2656
+ <div class="pos-details">
2657
+ <div class="pos-detail-item">
2658
+ <div class="pos-detail-label">Sistem</div>
2659
+ <div class="pos-detail-value">${pos.systemId?.name || 'Bilinmeyen Sistem'}</div>
2660
+ </div>
2661
+ <div class="pos-detail-item">
2662
+ <div class="pos-detail-label">Ölçüler</div>
2663
+ <div class="pos-detail-value">${pos.width}x${pos.height}mm</div>
2664
+ </div>
2665
+ <div class="pos-detail-item">
2666
+ <div class="pos-detail-label">Adet</div>
2667
+ <div class="pos-detail-value">${pos.quantity}</div>
2668
+ </div>
2669
+ <div class="pos-detail-item">
2670
+ <div class="pos-detail-label">Toplam Alan</div>
2671
+ <div class="pos-detail-value">${pos.glassInfo?.totalArea || '0.00'} m²</div>
2672
+ </div>
2673
+ <div class="pos-detail-item">
2674
+ <div class="pos-detail-label">Cari</div>
2675
+ <div class="pos-detail-value">${pos.customerName || 'Genel'}</div>
2676
+ </div>
2677
+ <div class="pos-detail-item">
2678
+ <div class="pos-detail-label">Tarih</div>
2679
+ <div class="pos-detail-value">${new Date(pos.createdAt).toLocaleDateString('tr-TR')}</div>
2680
+ </div>
2681
+ </div>
2682
+ <div class="pos-details">
2683
+ <div class="pos-detail-item">
2684
+ <div class="pos-detail-label">Yatay Profiller</div>
2685
+ <div class="pos-detail-value">
2686
+ ${(pos.horizontalParts || []).map(p => `${p.name}: ${p.size}mm (${p.quantity} adet)`).join('<br>')}
2687
+ </div>
2688
+ </div>
2689
+ <div class="pos-detail-item">
2690
+ <div class="pos-detail-label">Dikey Profiller</div>
2691
+ <div class="pos-detail-value">
2692
+ ${(pos.verticalParts || []).map(p => `${p.name}: ${p.size}mm (${p.quantity} adet)`).join('<br>')}
2693
+ </div>
2694
+ </div>
2695
+ <div class="pos-detail-item">
2696
+ <div class="pos-detail-label">Cam</div>
2697
+ <div class="pos-detail-value">${camInfoHtml}</div>
2698
+ </div>
2699
+ </div>
2700
+ </div>`;
2701
+ });
2702
+
2703
+ list.innerHTML = html;
2704
+ }
2705
+
2706
+ // Override calculation function
2707
+ async function calculateAndAddPos() {
2708
+ const systemId = document.getElementById('systemSelect').value;
2709
+ const width = parseInt(document.getElementById('width').value) || 0;
2710
+ const height = parseInt(document.getElementById('height').value) || 0;
2711
+ const quantity = parseInt(document.getElementById('quantity').value) || 1;
2712
+
2713
+ if (!systemId || !width || !height) {
2714
+ showError('Lütfen sistem ve geçerli ölçüler seçin!');
2715
+ return;
2716
+ }
2717
+
2718
+ const system = systems.find(s => (s._id || s.id) === systemId);
2719
+ if (!system) {
2720
+ showError('Sistem bulunamadı!');
2721
+ return;
2722
+ }
2723
+
2724
+ // Calculate parts
2725
+ const horizontalParts = system.parts?.filter(p => p.type === 'yatay').map(p => {
2726
+ let size = width - p.reduction;
2727
+ if (p.advancedFormula?.formula) {
2728
+ try {
2729
+ size = evaluateFormula(p.advancedFormula.formula, width, height);
2730
+ } catch (error) {
2731
+ console.error('Formül hatası:', error);
2732
+ }
2733
+ }
2734
+ return {
2735
+ name: p.description || p.name,
2736
+ size: Math.round(size),
2737
+ quantity: p.quantity * quantity,
2738
+ formulaUsed: !!p.advancedFormula?.formula
2739
+ };
2740
+ }) || [];
2741
+
2742
+ const verticalParts = system.parts?.filter(p => p.type === 'dikey').map(p => {
2743
+ let size = height - p.reduction;
2744
+ if (p.advancedFormula?.formula) {
2745
+ try {
2746
+ size = evaluateFormula(p.advancedFormula.formula, width, height);
2747
+ } catch (error) {
2748
+ console.error('Formül hatası:', error);
2749
+ }
2750
+ }
2751
+ return {
2752
+ name: p.description || p.name,
2753
+ size: Math.round(size),
2754
+ quantity: p.quantity * quantity,
2755
+ formulaUsed: !!p.advancedFormula?.formula
2756
+ };
2757
+ }) || [];
2758
+
2759
+ const camParts = system.parts?.filter(p => p.type === 'cam').map(camPart => {
2760
+ let glassWidth = width - 20;
2761
+ let glassHeight = height - 20;
2762
+
2763
+ if (camPart.glassFormulas) {
2764
+ try {
2765
+ glassWidth = evaluateFormula(camPart.glassFormulas.horizontal, width, height);
2766
+ glassHeight = evaluateFormula(camPart.glassFormulas.vertical, width, height);
2767
+ } catch (error) {
2768
+ console.error('Cam formülü hatası:', error);
2769
+ }
2770
+ }
2771
+
2772
+ const totalArea = (glassWidth * glassHeight * quantity / 1000000).toFixed(2);
2773
+
2774
+ return {
2775
+ name: camPart.description || camPart.name,
2776
+ quantity: camPart.quantity * quantity,
2777
+ size: `${Math.round(glassWidth)}x${Math.round(glassHeight)}`,
2778
+ width: Math.round(glassWidth),
2779
+ height: Math.round(glassHeight),
2780
+ totalArea: totalArea
2781
+ };
2782
+ }) || [];
2783
+
2784
+ const totalArea = camParts.length > 0 ? camParts[0].totalArea : '0.00';
2785
+
2786
+ const newPosition = {
2787
+ systemId: system._id,
2788
+ projectName: document.getElementById('projectName').value,
2789
+ width,
2790
+ height,
2791
+ quantity,
2792
+ customerName: document.getElementById('customerName').value,
2793
+ horizontalParts,
2794
+ verticalParts,
2795
+ glassParts: camParts,
2796
+ glassInfo: {
2797
+ width: camParts.length > 0 ? camParts[0].width : width - 20,
2798
+ height: camParts.length > 0 ? camParts[0].height : height - 20,
2799
+ totalArea
2800
+ },
2801
+ pozNumber: positions.length + 1
2802
+ };
2803
+
2804
+ try {
2805
+ const savedPosition = await api.createPosition(newPosition);
2806
+ positions.push(savedPosition);
2807
+ updatePosList();
2808
+ showSuccess('Hesaplama yapıldı ve poz listeye eklendi!');
2809
+
2810
+ // Clear form
2811
+ document.getElementById('width').value = '';
2812
+ document.getElementById('height').value = '';
2813
+ document.getElementById('quantity').value = '1';
2814
+ document.getElementById('projectName').value = '';
2815
+ document.getElementById('customerName').value = '';
2816
+
2817
+ } catch (error) {
2818
+ showError('Poz kaydedilemedi: ' + error.message);
2819
+ }
2820
+ }
2821
+
2822
+ // Override company functions
2823
+ async function updateCompanyInfoDisplay() {
2824
+ if (!companyData) return;
2825
+
2826
+ const elements = {
2827
+ companyNameDisplay: companyData.name,
2828
+ companyAddressDisplay: companyData.address || 'Adres belirtilmemiş',
2829
+ companyContactDisplay: [
2830
+ companyData.phone ? `📞 ${companyData.phone}` : '',
2831
+ companyData.email ? `✉️ ${companyData.email}` : ''
2832
+ ].filter(Boolean).join(' | ') || 'İletişim bilgisi belirtilmemiş',
2833
+ companyWebsiteDisplay: companyData.website ? `🌐 ${companyData.website}` : 'Website belirtilmemiş',
2834
+ companyDescriptionDisplay: companyData.description || 'Açıklama belirtilmemiş'
2835
+ };
2836
+
2837
+ Object.entries(elements).forEach(([id, value]) => {
2838
+ const element = document.getElementById(id);
2839
+ if (element) element.textContent = value;
2840
+ });
2841
+
2842
+ const logoDisplay = document.getElementById('companyLogoDisplay');
2843
+ if (logoDisplay) {
2844
+ logoDisplay.innerHTML = companyData.logo ? `<img src="${companyData.logo}" alt="Firma Logosu">` : '<div style="color: #a0aec0; font-size: 12px;">Logo yok</div>';
2845
+ }
2846
+
2847
+ const navbarLogo = document.getElementById('navCompanyLogo');
2848
+ if (navbarLogo) {
2849
+ if (companyData.logo) {
2850
+ navbarLogo.src = companyData.logo;
2851
+ navbarLogo.style.display = 'block';
2852
+ } else {
2853
+ navbarLogo.style.display = 'none';
2854
+ }
2855
+ }
2856
+ }
2857
+
2858
+ // Override test data function
2859
+ async function addTestData() {
2860
+ const testSystem = {
2861
+ name: 'PVC Pencere Sistemi',
2862
+ image: null,
2863
+ parts: [
2864
+ {
2865
+ name: 'Üst Profil',
2866
+ type: 'yatay',
2867
+ quantity: 1,
2868
+ reduction: 10,
2869
+ image: null,
2870
+ description: 'Pencerenin üst kısmında kullanılan yatay profil',
2871
+ advancedFormula: {
2872
+ formula: 'width - 10',
2873
+ type: 'fixed'
2874
+ }
2875
+ },
2876
+ {
2877
+ name: 'Alt Profil',
2878
+ type: 'yatay',
2879
+ quantity: 1,
2880
+ reduction: 10,
2881
+ image: null,
2882
+ description: 'Pencerenin alt kısmında kullanılan yatay profil',
2883
+ advancedFormula: {
2884
+ formula: 'width - 10',
2885
+ type: 'fixed'
2886
+ }
2887
+ },
2888
+ {
2889
+ name: 'Sol Profil',
2890
+ type: 'dikey',
2891
+ quantity: 1,
2892
+ reduction: 10,
2893
+ image: null,
2894
+ description: 'Pencerenin sol tarafında kullanılan dikey profil',
2895
+ advancedFormula: {
2896
+ formula: 'height - 10',
2897
+ type: 'fixed'
2898
+ }
2899
+ },
2900
+ {
2901
+ name: 'Sağ Profil',
2902
+ type: 'dikey',
2903
+ quantity: 1,
2904
+ reduction: 10,
2905
+ image: null,
2906
+ description: 'Pencerenin sağ tarafında kullanılan dikey profil',
2907
+ advancedFormula: {
2908
+ formula: 'height - 10',
2909
+ type: 'fixed'
2910
+ }
2911
+ },
2912
+ {
2913
+ name: 'Cam',
2914
+ type: 'cam',
2915
+ quantity: 1,
2916
+ reduction: 0,
2917
+ image: null,
2918
+ description: '4-16-4 İzolasyon Cam - Çift camlı izolasyon cam ünitesi',
2919
+ glassFormulas: {
2920
+ horizontal: 'width - 20',
2921
+ horizontalType: 'fixed',
2922
+ vertical: 'height - 20',
2923
+ verticalType: 'fixed'
2924
+ }
2925
+ }
2926
+ ]
2927
+ };
2928
+
2929
+ try {
2930
+ const savedSystem = await api.createSystem(testSystem);
2931
+ systems.push(savedSystem);
2932
+
2933
+ // Create test customer
2934
+ const testCustomer = {
2935
+ name: 'Test Cari',
2936
+ phone: '0555 555 55 55',
2937
+ email: 'test@cari.com',
2938
+ address: 'Test Adresi'
2939
+ };
2940
+
2941
+ const savedCustomer = await api.createCustomer(testCustomer);
2942
+ customerData[testCustomer.name] = {
2943
+ info: {
2944
+ phone: savedCustomer.phone,
2945
+ email: savedCustomer.email,
2946
+ address: savedCustomer.address,
2947
+ createdAt: savedCustomer.createdAt
2948
+ },
2949
+ pos: {}
2950
+ };
2951
+
2952
+ updateSystemSelect();
2953
+ updateCustomerList();
2954
+ renderCustomerList();
2955
+ updateNewCustomerButton();
2956
+
2957
+ showSuccess('Test verileri başarıyla oluşturuldu!');
2958
+
2959
+ } catch (error) {
2960
+ showError('Test verileri oluşturulamadı: ' + error.message);
2961
+ }
2962
+ }
2963
+
2964
+ // Override delete functions
2965
+ async function deletePos(index) {
2966
+ if (!confirm('Bu poz silinecek. Emin misiniz?')) return;
2967
+
2968
+ const position = positions[index];
2969
+ if (!position) return;
2970
+
2971
+ try {
2972
+ if (position._id) {
2973
+ await api.deletePosition(position._id);
2974
+ }
2975
+
2976
+ positions.splice(index, 1);
2977
+ updatePosList();
2978
+ showSuccess('Poz başarıyla silindi!');
2979
+ } catch (error) {
2980
+ showError('Poz silinemedi: ' + error.message);
2981
+ }
2982
+ }
2983
  // GÜNCELLENMİŞ PDF OLUŞTURMA FONKSİYONU - MM ve ADET sütunları yer değiştirdi + Firma bilgileri eklendi
2984
  const generateCalculationPDF = (calc) => {
2985
  const { jsPDF } = window.jspdf;
login.html ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="tr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pencere Ölçü Hesaplama - Giriş</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ body {
10
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
11
+ min-height: 100vh;
12
+ }
13
+ .login-container {
14
+ backdrop-filter: blur(10px);
15
+ background: rgba(255, 255, 255, 0.95);
16
+ }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <div class="min-h-screen flex items-center justify-center p-4">
21
+ <div class="login-container max-w-md w-full p-8 rounded-2xl shadow-2xl">
22
+ <div class="text-center mb-8">
23
+ <h1 class="text-3xl font-bold text-gray-800 mb-2">Pencere Ölçü Hesaplama</h1>
24
+ <p class="text-gray-600">Lütfen giriş yapın</p>
25
+ </div>
26
+
27
+ <form id="loginForm" class="space-y-6">
28
+ <div>
29
+ <label class="block text-sm font-medium text-gray-700 mb-2">Kullanıcı Adı</label>
30
+ <input type="text" id="username" required
31
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
32
+ placeholder="Kullanıcı adınızı girin">
33
+ </div>
34
+
35
+ <div>
36
+ <label class="block text-sm font-medium text-gray-700 mb-2">Şifre</label>
37
+ <input type="password" id="password" required
38
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
39
+ placeholder="Şifrenizi girin">
40
+ </div>
41
+
42
+ <button type="submit" id="loginBtn"
43
+ class="w-full bg-purple-600 text-white py-3 rounded-lg font-medium hover:bg-purple-700 transition duration-200 flex items-center justify-center">
44
+ <span id="loginText">Giriş Yap</span>
45
+ <div id="loginSpinner" class="hidden animate-spin rounded-full h-5 w-5 border-b-2 border-white ml-2"></div>
46
+ </button>
47
+ </form>
48
+
49
+ <div class="mt-8 text-center">
50
+ <button onclick="showRegisterForm()" class="text-purple-600 hover:text-purple-800 text-sm">
51
+ Hesabınız yok mu? Kayıt olun
52
+ </button>
53
+ </div>
54
+
55
+ <!-- Kayıt Formu (Başlangıçta gizli) -->
56
+ <form id="registerForm" class="hidden space-y-6">
57
+ <div>
58
+ <label class="block text-sm font-medium text-gray-700 mb-2">Kullanıcı Adı</label>
59
+ <input type="text" id="regUsername" required
60
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
61
+ placeholder="Kullanıcı adınızı girin">
62
+ </div>
63
+
64
+ <div>
65
+ <label class="block text-sm font-medium text-gray-700 mb-2">E-posta</label>
66
+ <input type="email" id="regEmail" required
67
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
68
+ placeholder="E-posta adresinizi girin">
69
+ </div>
70
+
71
+ <div>
72
+ <label class="block text-sm font-medium text-gray-700 mb-2">Şifre</label>
73
+ <input type="password" id="regPassword" required
74
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
75
+ placeholder="Şifrenizi girin">
76
+ </div>
77
+
78
+ <button type="submit" id="registerBtn"
79
+ class="w-full bg-green-600 text-white py-3 rounded-lg font-medium hover:bg-green-700 transition duration-200 flex items-center justify-center">
80
+ <span id="registerText">Kayıt Ol</span>
81
+ <div id="registerSpinner" class="hidden animate-spin rounded-full h-5 w-5 border-b-2 border-white ml-2"></div>
82
+ </button>
83
+
84
+ <button type="button" onclick="showLoginForm()"
85
+ class="w-full bg-gray-300 text-gray-700 py-3 rounded-lg font-medium hover:bg-gray-400 transition duration-200">
86
+ Giriş formuna geri dön
87
+ </button>
88
+ </form>
89
+
90
+ <div id="errorMessage" class="hidden mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
91
+ <span id="errorText"></span>
92
+ </div>
93
+
94
+ <div id="successMessage" class="hidden mt-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded-lg">
95
+ <span id="successText"></span>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <script src="api-client.js"></script>
101
+ <script>
102
+ // Form switching
103
+ function showRegisterForm() {
104
+ document.getElementById('loginForm').classList.add('hidden');
105
+ document.getElementById('registerForm').classList.remove('hidden');
106
+ document.getElementById('errorMessage').classList.add('hidden');
107
+ document.getElementById('successMessage').classList.add('hidden');
108
+ }
109
+
110
+ function showLoginForm() {
111
+ document.getElementById('registerForm').classList.add('hidden');
112
+ document.getElementById('loginForm').classList.remove('hidden');
113
+ document.getElementById('errorMessage').classList.add('hidden');
114
+ document.getElementById('successMessage').classList.add('hidden');
115
+ }
116
+
117
+ // Show error message
118
+ function showError(message) {
119
+ const errorDiv = document.getElementById('errorMessage');
120
+ const errorText = document.getElementById('errorText');
121
+ errorText.textContent = message;
122
+ errorDiv.classList.remove('hidden');
123
+
124
+ setTimeout(() => {
125
+ errorDiv.classList.add('hidden');
126
+ }, 5000);
127
+ }
128
+
129
+ // Show success message
130
+ function showSuccess(message) {
131
+ const successDiv = document.getElementById('successMessage');
132
+ const successText = document.getElementById('successText');
133
+ successText.textContent = message;
134
+ successDiv.classList.remove('hidden');
135
+
136
+ setTimeout(() => {
137
+ successDiv.classList.add('hidden');
138
+ }, 5000);
139
+ }
140
+
141
+ // Toggle loading state
142
+ function setLoading(buttonId, spinnerId, textId, loading = true) {
143
+ const button = document.getElementById(buttonId);
144
+ const spinner = document.getElementById(spinnerId);
145
+ const text = document.getElementById(textId);
146
+
147
+ if (loading) {
148
+ button.disabled = true;
149
+ spinner.classList.remove('hidden');
150
+ text.textContent = 'İşleniyor...';
151
+ } else {
152
+ button.disabled = false;
153
+ spinner.classList.add('hidden');
154
+
155
+ if (buttonId === 'loginBtn') {
156
+ text.textContent = 'Giriş Yap';
157
+ } else if (buttonId === 'registerBtn') {
158
+ text.textContent = 'Kayıt Ol';
159
+ }
160
+ }
161
+ }
162
+
163
+ // Login form submission
164
+ document.getElementById('loginForm').addEventListener('submit', async (e) => {
165
+ e.preventDefault();
166
+
167
+ const username = document.getElementById('username').value.trim();
168
+ const password = document.getElementById('password').value;
169
+
170
+ if (!username || !password) {
171
+ showError('Lütfen tüm alanları doldurun.');
172
+ return;
173
+ }
174
+
175
+ setLoading('loginBtn', 'loginSpinner', 'loginText', true);
176
+
177
+ try {
178
+ const result = await api.login(username, password);
179
+ showSuccess('Giriş başarılı! Yönlendiriliyorsunuz...');
180
+
181
+ setTimeout(() => {
182
+ window.location.href = '/index.html';
183
+ }, 1500);
184
+ } catch (error) {
185
+ showError(error.message || 'Giriş başarısız oldu. Lütfen tekrar deneyin.');
186
+ } finally {
187
+ setLoading('loginBtn', 'loginSpinner', 'loginText', false);
188
+ }
189
+ });
190
+
191
+ // Register form submission
192
+ document.getElementById('registerForm').addEventListener('submit', async (e) => {
193
+ e.preventDefault();
194
+
195
+ const username = document.getElementById('regUsername').value.trim();
196
+ const email = document.getElementById('regEmail').value.trim();
197
+ const password = document.getElementById('regPassword').value;
198
+
199
+ if (!username || !email || !password) {
200
+ showError('Lütfen tüm alanları doldurun.');
201
+ return;
202
+ }
203
+
204
+ if (password.length < 6) {
205
+ showError('Şifre en az 6 karakter olmalıdır.');
206
+ return;
207
+ }
208
+
209
+ setLoading('registerBtn', 'registerSpinner', 'registerText', true);
210
+
211
+ try {
212
+ await api.register({ username, email, password });
213
+ showSuccess('Kayıt başarılı! Lütfen giriş yapın.');
214
+
215
+ // Clear form and switch to login
216
+ document.getElementById('regUsername').value = '';
217
+ document.getElementById('regEmail').value = '';
218
+ document.getElementById('regPassword').value = '';
219
+
220
+ setTimeout(() => {
221
+ showLoginForm();
222
+ }, 1500);
223
+ } catch (error) {
224
+ showError(error.message || 'Kayıt başarısız oldu. Lütfen tekrar deneyin.');
225
+ } finally {
226
+ setLoading('registerBtn', 'registerSpinner', 'registerText', false);
227
+ }
228
+ });
229
+
230
+ // Check if user is already logged in
231
+ document.addEventListener('DOMContentLoaded', () => {
232
+ if (api.isAuthenticated()) {
233
+ window.location.href = '/index.html';
234
+ }
235
+ });
236
+ </script>
237
+ </body>
238
+ </html>
package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```json
2
+ {
3
+ "name": "pencere-hesaplama-server",
4
+ "version": "1.0.0",
5
+ "description": "Pencere Ölçü Hesaplama Server",
6
+ "main": "server.js",
7
+ "scripts": {
8
+ "start": "node server.js",
9
+ "dev": "nodemon server.js",
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": ["pencere", "hesaplama", "construction", "database"],
13
+ "author": "Your Name",
14
+ "license": "ISC",
15
+ "dependencies": {
16
+ "express": "^4.18.2",
17
+ "mongoose": "^7.5.0",
18
+ "cors": "^2.8.5",
19
+ "bcryptjs": "^2.4.3",
20
+ "jsonwebtoken": "^9.0.2",
21
+ "multer": "^1.4.5-lts.1",
22
+ "dotenv": "^16.3.1"
23
+ },
24
+ "devDependencies": {
25
+ "nodemon": "^3.0.1"
26
+ }
27
+ }
28
+ ```
server.js ADDED
@@ -0,0 +1,617 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const mongoose = require('mongoose');
3
+ const cors = require('cors');
4
+ const bcrypt = require('bcryptjs');
5
+ const jwt = require('jsonwebtoken');
6
+ const multer = require('multer');
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ require('dotenv').config();
10
+
11
+ const app = express();
12
+ const PORT = process.env.PORT || 3001;
13
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-here';
14
+
15
+ // Middleware
16
+ app.use(cors());
17
+ app.use(express.json({ limit: '50mb' }));
18
+ app.use(express.urlencoded({ extended: true, limit: '50mb' }));
19
+
20
+ // Serve static files
21
+ app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
22
+
23
+ // Ensure uploads directory exists
24
+ const uploadsDir = path.join(__dirname, 'uploads');
25
+ if (!fs.existsSync(uploadsDir)) {
26
+ fs.mkdirSync(uploadsDir, { recursive: true });
27
+ }
28
+
29
+ // Multer configuration for file uploads
30
+ const storage = multer.diskStorage({
31
+ destination: (req, file, cb) => {
32
+ cb(null, 'uploads/');
33
+ },
34
+ filename: (req, file, cb) => {
35
+ const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
36
+ cb(null, uniqueSuffix + path.extname(file.originalname));
37
+ }
38
+ });
39
+
40
+ const upload = multer({
41
+ storage: storage,
42
+ limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit
43
+ fileFilter: (req, file, cb) => {
44
+ if (file.mimetype.startsWith('image/')) {
45
+ cb(null, true);
46
+ } else {
47
+ cb(new Error('Only image files are allowed'));
48
+ }
49
+ }
50
+ });
51
+
52
+ // MongoDB Connection
53
+ mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/pencere-hesaplama', {
54
+ useNewUrlParser: true,
55
+ useUnifiedTopology: true,
56
+ }).then(() => {
57
+ console.log('Connected to MongoDB');
58
+ }).catch(err => {
59
+ console.error('MongoDB connection error:', err);
60
+ });
61
+
62
+ // Schemas
63
+ const userSchema = new mongoose.Schema({
64
+ username: { type: String, required: true, unique: true },
65
+ password: { type: String, required: true },
66
+ email: { type: String, required: true, unique: true },
67
+ role: { type: String, enum: ['admin', 'user'], default: 'user' },
68
+ createdAt: { type: Date, default: Date.now }
69
+ });
70
+
71
+ const companySchema = new mongoose.Schema({
72
+ name: { type: String, required: true },
73
+ logo: { type: String },
74
+ address: { type: String },
75
+ phone: { type: String },
76
+ email: { type: String },
77
+ website: { type: String },
78
+ description: { type: String },
79
+ createdAt: { type: Date, default: Date.now }
80
+ });
81
+
82
+ const systemSchema = new mongoose.Schema({
83
+ name: { type: String, required: true },
84
+ image: { type: String },
85
+ parts: [{
86
+ name: { type: String, required: true },
87
+ type: { type: String, enum: ['yatay', 'dikey', 'cam'], required: true },
88
+ quantity: { type: Number, required: true, default: 1 },
89
+ reduction: { type: Number, default: 0 },
90
+ description: { type: String },
91
+ image: { type: String },
92
+ advancedFormula: {
93
+ formula: { type: String },
94
+ type: { type: String }
95
+ },
96
+ glassFormulas: {
97
+ horizontal: { type: String },
98
+ horizontalType: { type: String },
99
+ vertical: { type: String },
100
+ verticalType: { type: String }
101
+ },
102
+ createdAt: { type: Date, default: Date.now }
103
+ }],
104
+ createdAt: { type: Date, default: Date.now },
105
+ updatedAt: { type: Date, default: Date.now }
106
+ });
107
+
108
+ const customerSchema = new mongoose.Schema({
109
+ name: { type: String, required: true, unique: true },
110
+ phone: { type: String },
111
+ email: { type: String },
112
+ address: { type: String },
113
+ createdAt: { type: Date, default: Date.now },
114
+ updatedAt: { type: Date, default: Date.now }
115
+ });
116
+
117
+ const positionSchema = new mongoose.Schema({
118
+ customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: false },
119
+ projectId: { type: mongoose.Schema.Types.ObjectId, ref: 'Project' },
120
+ systemId: { type: mongoose.Schema.Types.ObjectId, ref: 'System', required: true },
121
+ projectName: { type: String },
122
+ width: { type: Number, required: true },
123
+ height: { type: Number, required: true },
124
+ quantity: { type: Number, required: true, default: 1 },
125
+ customerName: { type: String },
126
+ horizontalParts: [{
127
+ name: { type: String },
128
+ size: { type: Number },
129
+ quantity: { type: Number },
130
+ formulaUsed: { type: Boolean }
131
+ }],
132
+ verticalParts: [{
133
+ name: { type: String },
134
+ size: { type: Number },
135
+ quantity: { type: Number },
136
+ formulaUsed: { type: Boolean }
137
+ }],
138
+ glassParts: [{
139
+ name: { type: String },
140
+ quantity: { type: Number },
141
+ size: { type: String },
142
+ width: { type: Number },
143
+ height: { type: Number },
144
+ totalArea: { type: String }
145
+ }],
146
+ glassInfo: {
147
+ width: { type: Number },
148
+ height: { type: Number },
149
+ totalArea: { type: String }
150
+ },
151
+ pozNumber: { type: Number },
152
+ createdAt: { type: Date, default: Date.now },
153
+ updatedAt: { type: Date, default: Date.now }
154
+ });
155
+
156
+ const projectSchema = new mongoose.Schema({
157
+ name: { type: String, required: true },
158
+ customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer' },
159
+ description: { type: String },
160
+ startDate: { type: Date },
161
+ endDate: { type: Date },
162
+ status: { type: String, enum: ['active', 'completed', 'cancelled'], default: 'active' },
163
+ createdAt: { type: Date, default: Date.now },
164
+ updatedAt: { type: Date, default: Date.now }
165
+ });
166
+
167
+ const pdfSettingsSchema = new mongoose.Schema({
168
+ settings: { type: Object, required: true },
169
+ type: { type: String, enum: ['global', 'perpage'], required: true },
170
+ isActive: { type: Boolean, default: true },
171
+ createdAt: { type: Date, default: Date.now },
172
+ updatedAt: { type: Date, default: Date.now }
173
+ });
174
+
175
+ // Models
176
+ const User = mongoose.model('User', userSchema);
177
+ const Company = mongoose.model('Company', companySchema);
178
+ const System = mongoose.model('System', systemSchema);
179
+ const Customer = mongoose.model('Customer', customerSchema);
180
+ const Position = mongoose.model('Position', positionSchema);
181
+ const Project = mongoose.model('Project', projectSchema);
182
+ const PDFSettings = mongoose.model('PDFSettings', pdfSettingsSchema);
183
+
184
+ // Authentication Middleware
185
+ const authenticateToken = (req, res, next) => {
186
+ const authHeader = req.headers['authorization'];
187
+ const token = authHeader && authHeader.split(' ')[1];
188
+
189
+ if (!token) {
190
+ return res.status(401).json({ error: 'Access token required' });
191
+ }
192
+
193
+ jwt.verify(token, JWT_SECRET, (err, user) => {
194
+ if (err) {
195
+ return res.status(403).json({ error: 'Invalid token' });
196
+ }
197
+ req.user = user;
198
+ next();
199
+ });
200
+ };
201
+
202
+ // Helper Functions
203
+ const generatePozNumber = async (customerId = null) => {
204
+ const query = customerId ? { customerId } : { customerId: null };
205
+ const lastPosition = await Position.findOne(query).sort({ pozNumber: -1 });
206
+ return lastPosition ? lastPosition.pozNumber + 1 : 1;
207
+ };
208
+
209
+ // Auth Routes
210
+ app.post('/api/auth/register', async (req, res) => {
211
+ try {
212
+ const { username, email, password } = req.body;
213
+
214
+ if (!username || !email || !password) {
215
+ return res.status(400).json({ error: 'All fields are required' });
216
+ }
217
+
218
+ const existingUser = await User.findOne({
219
+ $or: [{ username }, { email }]
220
+ });
221
+
222
+ if (existingUser) {
223
+ return res.status(400).json({ error: 'Username or email already exists' });
224
+ }
225
+
226
+ const hashedPassword = await bcrypt.hash(password, 10);
227
+ const user = new User({ username, email, password: hashedPassword });
228
+ await user.save();
229
+
230
+ const token = jwt.sign(
231
+ { userId: user._id, username: user.username, role: user.role },
232
+ JWT_SECRET,
233
+ { expiresIn: '24h' }
234
+ );
235
+
236
+ res.status(201).json({
237
+ message: 'User created successfully',
238
+ token,
239
+ user: { id: user._id, username: user.username, email: user.email, role: user.role }
240
+ });
241
+ } catch (error) {
242
+ res.status(500).json({ error: error.message });
243
+ }
244
+ });
245
+
246
+ app.post('/api/auth/login', async (req, res) => {
247
+ try {
248
+ const { username, password } = req.body;
249
+
250
+ if (!username || !password) {
251
+ return res.status(400).json({ error: 'Username and password are required' });
252
+ }
253
+
254
+ const user = await User.findOne({ username });
255
+ if (!user) {
256
+ return res.status(401).json({ error: 'Invalid credentials' });
257
+ }
258
+
259
+ const isMatch = await bcrypt.compare(password, user.password);
260
+ if (!isMatch) {
261
+ return res.status(401).json({ error: 'Invalid credentials' });
262
+ }
263
+
264
+ const token = jwt.sign(
265
+ { userId: user._id, username: user.username, role: user.role },
266
+ JWT_SECRET,
267
+ { expiresIn: '24h' }
268
+ );
269
+
270
+ res.json({
271
+ message: 'Login successful',
272
+ token,
273
+ user: { id: user._id, username: user.username, email: user.email, role: user.role }
274
+ });
275
+ } catch (error) {
276
+ res.status(500).json({ error: error.message });
277
+ }
278
+ });
279
+
280
+ // Company Routes
281
+ app.get('/api/company', async (req, res) => {
282
+ try {
283
+ let company = await Company.findOne();
284
+ if (!company) {
285
+ company = new Company({
286
+ name: 'Firma Adı',
287
+ createdAt: new Date()
288
+ });
289
+ await company.save();
290
+ }
291
+ res.json(company);
292
+ } catch (error) {
293
+ res.status(500).json({ error: error.message });
294
+ }
295
+ });
296
+
297
+ app.put('/api/company', upload.single('logo'), async (req, res) => {
298
+ try {
299
+ const updateData = { ...req.body };
300
+ if (req.file) {
301
+ updateData.logo = `/uploads/${req.file.filename}`;
302
+ }
303
+
304
+ const company = await Company.findOneAndUpdate(
305
+ {},
306
+ { $set: updateData, updatedAt: new Date() },
307
+ { new: true, upsert: true }
308
+ );
309
+
310
+ res.json(company);
311
+ } catch (error) {
312
+ res.status(500).json({ error: error.message });
313
+ }
314
+ });
315
+
316
+ // System Routes
317
+ app.get('/api/systems', async (req, res) => {
318
+ try {
319
+ const systems = await System.find().sort({ createdAt: -1 });
320
+ res.json(systems);
321
+ } catch (error) {
322
+ res.status(500).json({ error: error.message });
323
+ }
324
+ });
325
+
326
+ app.post('/api/systems', upload.single('image'), async (req, res) => {
327
+ try {
328
+ const systemData = { ...req.body };
329
+ if (req.file) {
330
+ systemData.image = `/uploads/${req.file.filename}`;
331
+ }
332
+
333
+ const system = new System(systemData);
334
+ await system.save();
335
+
336
+ res.status(201).json(system);
337
+ } catch (error) {
338
+ res.status(500).json({ error: error.message });
339
+ }
340
+ });
341
+
342
+ app.put('/api/systems/:id', upload.single('image'), async (req, res) => {
343
+ try {
344
+ const updateData = { ...req.body, updatedAt: new Date() };
345
+ if (req.file) {
346
+ updateData.image = `/uploads/${req.file.filename}`;
347
+ }
348
+
349
+ const system = await System.findByIdAndUpdate(
350
+ req.params.id,
351
+ updateData,
352
+ { new: true }
353
+ );
354
+
355
+ if (!system) {
356
+ return res.status(404).json({ error: 'System not found' });
357
+ }
358
+
359
+ res.json(system);
360
+ } catch (error) {
361
+ res.status(500).json({ error: error.message });
362
+ }
363
+ });
364
+
365
+ app.delete('/api/systems/:id', async (req, res) => {
366
+ try {
367
+ const system = await System.findByIdAndDelete(req.params.id);
368
+ if (!system) {
369
+ return res.status(404).json({ error: 'System not found' });
370
+ }
371
+
372
+ // Delete associated image file
373
+ if (system.image && fs.existsSync(path.join(__dirname, system.image))) {
374
+ fs.unlinkSync(path.join(__dirname, system.image));
375
+ }
376
+
377
+ res.json({ message: 'System deleted successfully' });
378
+ } catch (error) {
379
+ res.status(500).json({ error: error.message });
380
+ }
381
+ });
382
+
383
+ // Customer Routes
384
+ app.get('/api/customers', async (req, res) => {
385
+ try {
386
+ const customers = await Customer.find().sort({ name: 1 });
387
+ res.json(customers);
388
+ } catch (error) {
389
+ res.status(500).json({ error: error.message });
390
+ }
391
+ });
392
+
393
+ app.post('/api/customers', async (req, res) => {
394
+ try {
395
+ const customer = new Customer(req.body);
396
+ await customer.save();
397
+
398
+ res.status(201).json(customer);
399
+ } catch (error) {
400
+ if (error.code === 11000) {
401
+ res.status(400).json({ error: 'Customer name already exists' });
402
+ } else {
403
+ res.status(500).json({ error: error.message });
404
+ }
405
+ }
406
+ });
407
+
408
+ app.put('/api/customers/:id', async (req, res) => {
409
+ try {
410
+ const customer = await Customer.findByIdAndUpdate(
411
+ req.params.id,
412
+ { $set: req.body, updatedAt: new Date() },
413
+ { new: true }
414
+ );
415
+
416
+ if (!customer) {
417
+ return res.status(404).json({ error: 'Customer not found' });
418
+ }
419
+
420
+ res.json(customer);
421
+ } catch (error) {
422
+ if (error.code === 11000) {
423
+ res.status(400).json({ error: 'Customer name already exists' });
424
+ } else {
425
+ res.status(500).json({ error: error.message });
426
+ }
427
+ }
428
+ });
429
+
430
+ app.delete('/api/customers/:id', async (req, res) => {
431
+ try {
432
+ const customer = await Customer.findByIdAndDelete(req.params.id);
433
+ if (!customer) {
434
+ return res.status(404).json({ error: 'Customer not found' });
435
+ }
436
+
437
+ // Delete associated positions
438
+ await Position.deleteMany({ customerId: req.params.id });
439
+
440
+ res.json({ message: 'Customer deleted successfully' });
441
+ } catch (error) {
442
+ res.status(500).json({ error: error.message });
443
+ }
444
+ });
445
+
446
+ // Position Routes
447
+ app.get('/api/positions', async (req, res) => {
448
+ try {
449
+ const { customerId } = req.query;
450
+ const query = customerId ? { customerId } : { customerId: null };
451
+
452
+ const positions = await Position.find(query)
453
+ .populate('systemId', 'name image parts')
454
+ .sort({ createdAt: -1 });
455
+
456
+ res.json(positions);
457
+ } catch (error) {
458
+ res.status(500).json({ error: error.message });
459
+ }
460
+ });
461
+
462
+ app.post('/api/positions', async (req, res) => {
463
+ try {
464
+ const positionData = { ...req.body };
465
+
466
+ if (!positionData.pozNumber) {
467
+ positionData.pozNumber = await generatePozNumber(positionData.customerId);
468
+ }
469
+
470
+ const position = new Position(positionData);
471
+ await position.save();
472
+
473
+ await position.populate('systemId', 'name image parts');
474
+
475
+ res.status(201).json(position);
476
+ } catch (error) {
477
+ res.status(500).json({ error: error.message });
478
+ }
479
+ });
480
+
481
+ app.put('/api/positions/:id', async (req, res) => {
482
+ try {
483
+ const position = await Position.findByIdAndUpdate(
484
+ req.params.id,
485
+ { $set: req.body, updatedAt: new Date() },
486
+ { new: true }
487
+ ).populate('systemId', 'name image parts');
488
+
489
+ if (!position) {
490
+ return res.status(404).json({ error: 'Position not found' });
491
+ }
492
+
493
+ res.json(position);
494
+ } catch (error) {
495
+ res.status(500).json({ error: error.message });
496
+ }
497
+ });
498
+
499
+ app.delete('/api/positions/:id', async (req, res) => {
500
+ try {
501
+ const position = await Position.findByIdAndDelete(req.params.id);
502
+ if (!position) {
503
+ return res.status(404).json({ error: 'Position not found' });
504
+ }
505
+
506
+ res.json({ message: 'Position deleted successfully' });
507
+ } catch (error) {
508
+ res.status(500).json({ error: error.message });
509
+ }
510
+ });
511
+
512
+ // PDF Settings Routes
513
+ app.get('/api/pdf-settings', async (req, res) => {
514
+ try {
515
+ const { type } = req.query;
516
+ const query = type ? { type, isActive: true } : { isActive: true };
517
+
518
+ const settings = await PDFSettings.findOne(query).sort({ createdAt: -1 });
519
+ res.json(settings || { settings: {} });
520
+ } catch (error) {
521
+ res.status(500).json({ error: error.message });
522
+ }
523
+ });
524
+
525
+ app.put('/api/pdf-settings', async (req, res) => {
526
+ try {
527
+ const { type = 'global', settings } = req.body;
528
+
529
+ // Deactivate other settings of the same type
530
+ await PDFSettings.updateMany({ type }, { isActive: false });
531
+
532
+ const pdfSettings = new PDFSettings({ type, settings, isActive: true });
533
+ await pdfSettings.save();
534
+
535
+ res.status(201).json(pdfSettings);
536
+ } catch (error) {
537
+ res.status(500).json({ error: error.message });
538
+ }
539
+ });
540
+
541
+ // Backup Routes
542
+ app.get('/api/backup', async (req, res) => {
543
+ try {
544
+ const backup = {
545
+ timestamp: new Date().toISOString(),
546
+ systems: await System.find(),
547
+ customers: await Customer.find(),
548
+ positions: await Position.find(),
549
+ company: await Company.findOne(),
550
+ pdfSettings: await PDFSettings.find(),
551
+ projects: await Project.find()
552
+ };
553
+
554
+ res.setHeader('Content-Type', 'application/json');
555
+ res.setHeader('Content-Disposition', `attachment; filename="backup-${new Date().toISOString().split('T')[0]}.json"`);
556
+ res.json(backup);
557
+ } catch (error) {
558
+ res.status(500).json({ error: error.message });
559
+ }
560
+ });
561
+
562
+ app.post('/api/restore', async (req, res) => {
563
+ try {
564
+ const backup = req.body;
565
+
566
+ // Clear existing data
567
+ await Promise.all([
568
+ System.deleteMany({}),
569
+ Customer.deleteMany({}),
570
+ Position.deleteMany({}),
571
+ Company.deleteMany({}),
572
+ PDFSettings.deleteMany({}),
573
+ Project.deleteMany({})
574
+ ]);
575
+
576
+ // Restore data
577
+ if (backup.systems && backup.systems.length > 0) {
578
+ await System.insertMany(backup.systems);
579
+ }
580
+
581
+ if (backup.customers && backup.customers.length > 0) {
582
+ await Customer.insertMany(backup.customers);
583
+ }
584
+
585
+ if (backup.positions && backup.positions.length > 0) {
586
+ await Position.insertMany(backup.positions);
587
+ }
588
+
589
+ if (backup.company) {
590
+ await Company.create(backup.company);
591
+ }
592
+
593
+ if (backup.pdfSettings && backup.pdfSettings.length > 0) {
594
+ await PDFSettings.insertMany(backup.pdfSettings);
595
+ }
596
+
597
+ if (backup.projects && backup.projects.length > 0) {
598
+ await Project.insertMany(backup.projects);
599
+ }
600
+
601
+ res.json({ message: 'Data restored successfully' });
602
+ } catch (error) {
603
+ res.status(500).json({ error: error.message });
604
+ }
605
+ });
606
+
607
+ // Error handling middleware
608
+ app.use((err, req, res, next) => {
609
+ console.error(err.stack);
610
+ res.status(500).json({ error: 'Something went wrong!' });
611
+ });
612
+
613
+ // Start server
614
+ app.listen(PORT, () => {
615
+ console.log(`Server is running on port ${PORT}`);
616
+ console.log(`API documentation: http://localhost:${PORT}/api`);
617
+ });