Spaces:
Configuration error
Configuration error
sunucuya veritabanı kur
Browse files- .env.example +14 -0
- api-client.js +261 -0
- index.html +722 -10
- login.html +238 -0
- package.json +28 -0
- 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 |
-
<
|
|
|
|
| 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">
|
| 639 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1511 |
-
|
| 1512 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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();
|
|
|
|
| 2262 |
|
| 2263 |
-
//
|
| 2264 |
if (systems.length === 0) {
|
| 2265 |
-
|
|
|
|
|
|
|
| 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 |
+
});
|