Spaces:
Running
Running
هناك مشاكل كثيرة مثل ان السكربت كله لا يعمل و لا يوجد عرض يولد و كل الازرار لا تعمل - Follow Up Deployment
822dfb0 verified | <html lang="ar" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>نظام عروض الأسعار - المصرية للمقاولات الكهروميكانيكية</title> | |
| <script src="https://cdn.tailwindcss.com?plugins=forms"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #1e40af; | |
| --primary-hover: #1e3fa8; | |
| } | |
| @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap'); | |
| body { | |
| font-family: 'Tajawal', sans-serif; | |
| } | |
| .page { | |
| width: 21cm; | |
| min-height: 29.7cm; | |
| padding: 2cm; | |
| margin: 1cm auto; | |
| border: 1px solid #e5e7eb; | |
| box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); | |
| background: white; | |
| position: relative; | |
| } | |
| @media print { | |
| body, .page { | |
| margin: 0; | |
| box-shadow: none; | |
| } | |
| .no-print { | |
| display: none ; | |
| } | |
| } | |
| .editable { | |
| border-bottom: 1px dashed #999; | |
| min-width: 50px; | |
| display: inline-block; | |
| } | |
| .item-row:hover { | |
| background-color: #f5f5f5; | |
| } | |
| .company-logo { | |
| max-width: 150px; | |
| max-height: 80px; | |
| object-fit: contain; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100"> | |
| <div class="container mx-auto px-4 py-8 no-print"> | |
| <h1 class="text-3xl font-bold text-center mb-8 text-blue-800">نظام عروض الأسعار</h1> | |
| <div class="flex flex-wrap gap-4 mb-8"> | |
| <button id="newQuoteBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-file-alt"></i> عرض سعر جديد | |
| </button> | |
| <button id="settingsBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-cog"></i> الإعدادات | |
| </button> | |
| <button id="exportBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-file-export"></i> تصدير البيانات | |
| </button> | |
| <button id="importBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-file-import"></i> استيراد البيانات | |
| </button> | |
| </div> | |
| <!-- Main Input Section --> | |
| <div id="inputSection" class="bg-white rounded-lg shadow-md p-6 mb-8"> | |
| <h2 class="text-xl font-semibold mb-4 text-blue-700 border-b pb-2">بيانات عرض السعر</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> | |
| <div> | |
| <label class="block text-gray-700 mb-2">اسم الجهة</label> | |
| <input type="text" id="clientName" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">تاريخ العرض</label> | |
| <input type="date" id="quoteDate" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">رقم العرض</label> | |
| <input type="text" id="quoteNumber" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-gray-700 mb-2">صلاحية العرض (أيام)</label> | |
| <input type="number" id="validityDays" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" value="30"> | |
| </div> | |
| </div> | |
| <h2 class="text-xl font-semibold mb-4 text-blue-700 border-b pb-2">بنود العرض</h2> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full bg-white border"> | |
| <thead> | |
| <tr class="bg-gray-100"> | |
| <th class="py-2 px-4 border w-12">م</th> | |
| <th class="py-2 px-4 border w-24">رقم البند</th> | |
| <th class="py-2 px-4 border">البيان</th> | |
| <th class="py-2 px-4 border w-24">الوحدة</th> | |
| <th class="py-2 px-4 border w-24">الكمية</th> | |
| <th class="py-2 px-4 border w-32">سعر الوحدة</th> | |
| <th class="py-2 px-4 border w-32">الإجمالي</th> | |
| <th class="py-2 px-4 border w-12"></th> | |
| </tr> | |
| </thead> | |
| <tbody id="itemsTable"> | |
| <!-- Items will be added here --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <button id="addItemBtn" class="mt-4 bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-plus"></i> إضافة بند | |
| </button> | |
| <div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-gray-700 mb-2">المجموع قبل الضريبة</h3> | |
| <div id="subtotal" class="text-xl font-bold">0.00</div> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-gray-700 mb-2">الضريبة (14%)</h3> | |
| <div id="taxAmount" class="text-xl font-bold">0.00</div> | |
| </div> | |
| <div class="bg-blue-50 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-blue-700 mb-2">المجموع بعد الضريبة</h3> | |
| <div id="totalAmount" class="text-2xl font-bold text-blue-800">0.00</div> | |
| </div> | |
| </div> | |
| <div class="mt-6"> | |
| <label class="block text-gray-700 mb-2">ملاحظات إضافية</label> | |
| <textarea id="notes" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea> | |
| </div> | |
| <div class="mt-6 flex flex-wrap gap-4"> | |
| <button id="generateQuoteBtn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-file-invoice"></i> توليد عرض الأسعار | |
| </button> | |
| <button id="previewBtn" class="bg-yellow-500 hover:bg-yellow-600 text-white px-6 py-3 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-eye"></i> معاينة | |
| </button> | |
| <button id="saveDraftBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-6 py-3 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-save"></i> حفظ مسودة | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Preview Section --> | |
| <div id="previewSection" class="hidden bg-white rounded-lg shadow-md p-6 mb-8"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-blue-700">معاينة عرض الأسعار</h2> | |
| <button id="backToInputBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-arrow-left"></i> العودة للإدخال | |
| </button> | |
| </div> | |
| <div class="flex gap-4 mb-4"> | |
| <button id="exportPdfBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-file-pdf"></i> تصدير PDF | |
| </button> | |
| <button id="exportWordBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-file-word"></i> تصدير Word | |
| </button> | |
| <button id="exportExcelBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-file-excel"></i> تصدير Excel | |
| </button> | |
| <button id="printBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-print"></i> طباعة | |
| </button> | |
| </div> | |
| <div id="quotePreview" class="border border-gray-300 p-2 bg-white"> | |
| <!-- Quote will be rendered here --> | |
| </div> | |
| </div> | |
| <!-- Settings Modal --> | |
| <div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] overflow-y-auto"> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">إعدادات النظام</h2> | |
| <button id="closeSettingsBtn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div> | |
| <h3 class="font-semibold text-lg mb-3 border-b pb-2">بيانات الشركة</h3> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">اسم الشركة</label> | |
| <input type="text" id="companyName" class="w-full px-4 py-2 border rounded-lg" value="المصرية للمقاولات الكهروميكانيكية"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">العنوان</label> | |
| <input type="text" id="companyAddress" class="w-full px-4 py-2 border rounded-lg"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">الهاتف</label> | |
| <input type="text" id="companyPhone" class="w-full px-4 py-2 border rounded-lg"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">البريد الإلكتروني</label> | |
| <input type="email" id="companyEmail" class="w-full px-4 py-2 border rounded-lg"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">السجل التجاري</label> | |
| <input type="text" id="companyCommercialRegister" class="w-full px-4 py-2 border rounded-lg"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">الرقم الضريبي</label> | |
| <input type="text" id="companyTaxId" class="w-full px-4 py-2 border rounded-lg"> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold text-lg mb-3 border-b pb-2">الشعار والتصميم</h3> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">شعار الشركة</label> | |
| <div class="flex items-center gap-4"> | |
| <img id="logoPreview" src="" alt="Company Logo" class="company-logo border rounded hidden"> | |
| <div> | |
| <input type="file" id="logoUpload" accept="image/*" class="hidden"> | |
| <button id="uploadLogoBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg"> | |
| <i class="fas fa-upload mr-2"></i> رفع شعار | |
| </button> | |
| <button id="removeLogoBtn" class="text-red-500 hover:text-red-700 ml-2 hidden"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">لون التصميم الأساسي</label> | |
| <input type="color" id="primaryColor" value="#1e40af" class="w-16 h-10"> | |
| </div> | |
| <h3 class="font-semibold text-lg mb-3 border-b pb-2 mt-6">الشروط والبيانات</h3> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">شروط الدفع</label> | |
| <textarea id="paymentTerms" rows="3" class="w-full px-4 py-2 border rounded-lg"></textarea> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">شروط التسليم</label> | |
| <textarea id="deliveryTerms" rows="3" class="w-full px-4 py-2 border rounded-lg"></textarea> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">ملاحظات عامة</label> | |
| <textarea id="generalNotes" rows="3" class="w-full px-4 py-2 border rounded-lg"></textarea> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-end gap-4"> | |
| <button id="cancelSettingsBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-6 py-2 rounded-lg"> | |
| إلغاء | |
| </button> | |
| <button id="saveSettingsBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg"> | |
| حفظ الإعدادات | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Quote Template (hidden by default) --> | |
| <div id="quoteTemplate" class="hidden"> | |
| <div class="page"> | |
| <div class="flex justify-between items-start mb-8"> | |
| <div> | |
| <div class="flex items-center gap-4"> | |
| <img id="quoteLogo" src="" alt="Company Logo" class="company-logo"> | |
| <div> | |
| <h1 id="quoteCompanyName" class="text-2xl font-bold text-blue-800">المصرية للمقاولات الكهروميكانيكية</h1> | |
| <div id="quoteCompanyDetails" class="text-sm text-gray-600"> | |
| <!-- Company details will be filled here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-left"> | |
| <h2 class="text-xl font-bold text-blue-800">عرض سعر</h2> | |
| <div class="text-sm text-gray-600"> | |
| <div>رقم: <span id="quoteNumberDisplay" class="font-semibold"></span></div> | |
| <div>التاريخ: <span id="quoteDateDisplay" class="font-semibold"></span></div> | |
| <div>الصلاحية: <span id="quoteValidityDisplay" class="font-semibold"></span> يوم</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-8"> | |
| <div class="bg-blue-50 p-3 rounded-t-lg"> | |
| <h3 class="font-semibold text-blue-800">السادة / <span id="quoteClientName" class="editable">اسم الجهة</span></h3> | |
| </div> | |
| <div class="border border-t-0 p-3 rounded-b-lg"> | |
| <p>تحية طيبة وبعد،،</p> | |
| <p class="mt-2">نقدم لكم عرضنا الفني والمالي للبنود التالية:</p> | |
| </div> | |
| </div> | |
| <div class="mb-8 overflow-x-auto"> | |
| <table class="w-full border-collapse"> | |
| <thead> | |
| <tr class="bg-gray-100"> | |
| <th class="py-2 px-4 border text-center w-12">م</th> | |
| <th class="py-2 px-4 border text-center w-24">رقم البند</th> | |
| <th class="py-2 px-4 border text-right">البيان</th> | |
| <th class="py-2 px-4 border text-center w-24">الوحدة</th> | |
| <th class="py-2 px-4 border text-center w-24">الكمية</th> | |
| <th class="py-2 px-4 border text-center w-32">سعر الوحدة</th> | |
| <th class="py-2 px-4 border text-center w-32">الإجمالي</th> | |
| </tr> | |
| </thead> | |
| <tbody id="quoteItemsTable"> | |
| <!-- Quote items will be added here --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div id="quoteNotesSection" class="mb-8"> | |
| <h4 class="font-semibold mb-2 border-b pb-1">ملاحظات:</h4> | |
| <div id="quoteNotesContent" class="text-sm"> | |
| <!-- Notes will be added here --> | |
| </div> | |
| </div> | |
| <div id="quoteTotalsSection" class="mb-8"> | |
| <div class="flex justify-end"> | |
| <div class="w-64"> | |
| <div class="flex justify-between py-2 border-b"> | |
| <span>المجموع قبل الضريبة:</span> | |
| <span id="quoteSubtotal" class="font-semibold">0.00</span> | |
| </div> | |
| <div class="flex justify-between py-2 border-b"> | |
| <span>الضريبة (14%):</span> | |
| <span id="quoteTax" class="font-semibold">0.00</span> | |
| </div> | |
| <div class="flex justify-between py-2 text-lg font-bold bg-blue-50 px-2"> | |
| <span>المجموع النهائي:</span> | |
| <span id="quoteTotal" class="text-blue-800">0.00</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="quoteTermsSection" class="mb-8"> | |
| <h4 class="font-semibold mb-2 border-b pb-1">شروط الدفع:</h4> | |
| <div id="quotePaymentTerms" class="text-sm"> | |
| <!-- Payment terms will be added here --> | |
| </div> | |
| <h4 class="font-semibold mb-2 border-b pb-1 mt-4">شروط التسليم:</h4> | |
| <div id="quoteDeliveryTerms" class="text-sm"> | |
| <!-- Delivery terms will be added here --> | |
| </div> | |
| </div> | |
| <div class="mt-12 flex justify-between items-center"> | |
| <div class="text-center"> | |
| <div class="mb-2">مقدم العرض</div> | |
| <div class="border-t w-32 mx-auto pt-2">التوقيع</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="mb-2">المستفيد</div> | |
| <div class="border-t w-32 mx-auto pt-2">التوقيع</div> | |
| </div> | |
| </div> | |
| <div class="mt-8 text-center text-xs text-gray-500"> | |
| <p>شكراً لثقتكم في خدماتنا - نرجو التواصل معنا لأي استفسارات</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize variables | |
| let items = []; | |
| let currentItemId = 1; | |
| let companySettings = { | |
| name: "المصرية للمقاولات الكهروميكانيكية", | |
| address: "", | |
| phone: "", | |
| email: "", | |
| commercialRegister: "", | |
| taxId: "", | |
| logo: "", | |
| primaryColor: "#1e40af", | |
| paymentTerms: "", | |
| deliveryTerms: "", | |
| generalNotes: "" | |
| }; | |
| // Load settings from localStorage if available | |
| function loadSettings() { | |
| const savedSettings = localStorage.getItem('companySettings'); | |
| if (savedSettings) { | |
| companySettings = JSON.parse(savedSettings); | |
| updateSettingsForm(); | |
| } | |
| // Apply primary color | |
| document.documentElement.style.setProperty('--tw-ring-color', companySettings.primaryColor); | |
| document.documentElement.style.setProperty('--tw-bg-opacity', '1'); | |
| document.documentElement.style.setProperty('--tw-text-opacity', '1'); | |
| } | |
| // Save settings to localStorage | |
| function saveSettings() { | |
| localStorage.setItem('companySettings', JSON.stringify(companySettings)); | |
| } | |
| // Update settings form with current values | |
| function updateSettingsForm() { | |
| document.getElementById('companyName').value = companySettings.name; | |
| document.getElementById('companyAddress').value = companySettings.address; | |
| document.getElementById('companyPhone').value = companySettings.phone; | |
| document.getElementById('companyEmail').value = companySettings.email; | |
| document.getElementById('companyCommercialRegister').value = companySettings.commercialRegister; | |
| document.getElementById('companyTaxId').value = companySettings.taxId; | |
| document.getElementById('primaryColor').value = companySettings.primaryColor; | |
| document.getElementById('paymentTerms').value = companySettings.paymentTerms; | |
| document.getElementById('deliveryTerms').value = companySettings.deliveryTerms; | |
| document.getElementById('generalNotes').value = companySettings.generalNotes; | |
| if (companySettings.logo) { | |
| document.getElementById('logoPreview').src = companySettings.logo; | |
| document.getElementById('logoPreview').classList.remove('hidden'); | |
| document.getElementById('removeLogoBtn').classList.remove('hidden'); | |
| } | |
| } | |
| // Add a new item to the table | |
| function addItem(item = null) { | |
| const itemId = currentItemId++; | |
| const newItem = item || { | |
| id: itemId, | |
| itemNumber: '', | |
| description: '', | |
| unit: '', | |
| quantity: 1, | |
| unitPrice: 0, | |
| total: 0 | |
| }; | |
| items.push(newItem); | |
| const row = document.createElement('tr'); | |
| row.className = 'item-row'; | |
| row.dataset.id = newItem.id; | |
| row.innerHTML = ` | |
| <td class="py-2 px-4 border text-center">${items.length}</td> | |
| <td class="py-2 px-4 border"><input type="text" class="item-number w-full px-2 py-1 border rounded" value="${newItem.itemNumber}"></td> | |
| <td class="py-2 px-4 border"><input type="text" class="item-description w-full px-2 py-1 border rounded" value="${newItem.description}"></td> | |
| <td class="py-2 px-4 border"><input type="text" class="item-unit w-full px-2 py-1 border rounded" value="${newItem.unit}"></td> | |
| <td class="py-2 px-4 border"><input type="number" min="1" class="item-quantity w-full px-2 py-1 border rounded" value="${newItem.quantity}"></td> | |
| <td class="py-2 px-4 border"><input type="number" min="0" step="0.01" class="item-unit-price w-full px-2 py-1 border rounded" value="${newItem.unitPrice}"></td> | |
| <td class="py-2 px-4 border text-right"><span class="item-total">${newItem.total.toFixed(2)}</span></td> | |
| <td class="py-2 px-4 border text-center"><button class="delete-item text-red-500 hover:text-red-700"><i class="fas fa-trash"></i></button></td> | |
| `; | |
| document.getElementById('itemsTable').appendChild(row); | |
| // Add event listeners to the new inputs | |
| const quantityInput = row.querySelector('.item-quantity'); | |
| const unitPriceInput = row.querySelector('.item-unit-price'); | |
| quantityInput.addEventListener('input', calculateItemTotal); | |
| unitPriceInput.addEventListener('input', calculateItemTotal); | |
| row.querySelector('.delete-item').addEventListener('click', function() { | |
| deleteItem(newItem.id); | |
| }); | |
| // Calculate total if this is a new item | |
| if (!item) { | |
| calculateItemTotal.call(quantityInput); | |
| } | |
| return newItem; | |
| } | |
| // Calculate item total | |
| function calculateItemTotal() { | |
| const row = this.closest('tr'); | |
| let quantity = parseFloat(row.querySelector('.item-quantity').value); | |
| let unitPrice = parseFloat(row.querySelector('.item-unit-price').value); | |
| if (isNaN(quantity) || quantity < 0) quantity = 0; | |
| if (isNaN(unitPrice) || unitPrice < 0) unitPrice = 0; | |
| const total = quantity * unitPrice; | |
| row.querySelector('.item-total').textContent = total.toFixed(2); | |
| // Update the item in the items array | |
| const itemId = parseInt(row.dataset.id); | |
| const item = items.find(item => item.id === itemId); | |
| if (item) { | |
| item.quantity = quantity; | |
| item.unitPrice = unitPrice; | |
| item.total = total; | |
| item.itemNumber = row.querySelector('.item-number').value; | |
| item.description = row.querySelector('.item-description').value; | |
| item.unit = row.querySelector('.item-unit').value; | |
| } | |
| calculateTotals(); | |
| } | |
| // Delete an item | |
| function deleteItem(itemId) { | |
| items = items.filter(item => item.id !== itemId); | |
| document.querySelector(`tr[data-id="${itemId}"]`).remove(); | |
| // Update serial numbers | |
| const rows = document.querySelectorAll('#itemsTable tr'); | |
| rows.forEach((row, index) => { | |
| row.cells[0].textContent = index + 1; | |
| }); | |
| calculateTotals(); | |
| } | |
| // Calculate all totals | |
| function calculateTotals() { | |
| const subtotal = items.reduce((sum, item) => sum + item.total, 0); | |
| const tax = subtotal * 0.14; | |
| const total = subtotal + tax; | |
| document.getElementById('subtotal').textContent = subtotal.toFixed(2); | |
| document.getElementById('taxAmount').textContent = tax.toFixed(2); | |
| document.getElementById('totalAmount').textContent = total.toFixed(2); | |
| } | |
| // Generate the quote | |
| function generateQuote() { | |
| const clientName = document.getElementById('clientName').value; | |
| const quoteDate = document.getElementById('quoteDate').value; | |
| const quoteNumber = document.getElementById('quoteNumber').value; | |
| const validityDays = document.getElementById('validityDays').value || 30; | |
| const notes = document.getElementById('notes').value; | |
| if (!clientName) { | |
| alert('الرجاء إدخال اسم الجهة'); | |
| return; | |
| } | |
| if (!quoteDate) { | |
| alert('الرجاء إدخال تاريخ العرض'); | |
| return; | |
| } | |
| if (!quoteNumber) { | |
| alert('الرجاء إدخال رقم العرض'); | |
| return; | |
| } | |
| if (items.length === 0) { | |
| alert('الرجاء إضافة بنود للعرض'); | |
| return; | |
| } | |
| // Format the date | |
| const formattedDate = formatDate(quoteDate); | |
| // Clear previous quote | |
| const quotePreview = document.getElementById('quotePreview'); | |
| quotePreview.innerHTML = ''; | |
| // Split items into pages (7 items per page) | |
| const itemsPerPage = 7; | |
| const pageCount = Math.ceil(items.length / itemsPerPage); | |
| // Always show at least one page even if no items | |
| const actualPageCount = Math.max(1, pageCount); | |
| for (let i = 0; i < actualPageCount; i++) { | |
| const pageItems = items.slice(i * itemsPerPage, (i + 1) * itemsPerPage); | |
| // Clone the quote template | |
| const quotePage = document.getElementById('quoteTemplate').cloneNode(true); | |
| quotePage.id = `quotePage${i + 1}`; | |
| quotePage.classList.remove('hidden'); | |
| // Fill in the header info | |
| quotePage.querySelector('#quoteClientName').textContent = clientName; | |
| quotePage.querySelector('#quoteNumberDisplay').textContent = quoteNumber; | |
| quotePage.querySelector('#quoteDateDisplay').textContent = formattedDate; | |
| quotePage.querySelector('#quoteValidityDisplay').textContent = validityDays; | |
| // Fill company info | |
| quotePage.querySelector('#quoteCompanyName').textContent = companySettings.name; | |
| let companyDetails = []; | |
| if (companySettings.address) companyDetails.push(companySettings.address); | |
| if (companySettings.phone) companyDetails.push(`ت: ${companySettings.phone}`); | |
| if (companySettings.email) companyDetails.push(`بريد: ${companySettings.email}`); | |
| if (companySettings.commercialRegister) companyDetails.push(`سجل تجاري: ${companySettings.commercialRegister}`); | |
| if (companySettings.taxId) companyDetails.push(`رقم ضريبي: ${companySettings.taxId}`); | |
| quotePage.querySelector('#quoteCompanyDetails').textContent = companyDetails.join(' - '); | |
| // Set logo if available | |
| if (companySettings.logo) { | |
| quotePage.querySelector('#quoteLogo').src = companySettings.logo; | |
| quotePage.querySelector('#quoteLogo').classList.remove('hidden'); | |
| } | |
| // Add items to the table | |
| const itemsTable = quotePage.querySelector('#quoteItemsTable'); | |
| itemsTable.innerHTML = ''; | |
| pageItems.forEach((item, index) => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td class="py-2 px-4 border text-center">${(i * itemsPerPage) + index + 1}</td> | |
| <td class="py-2 px-4 border text-center">${item.itemNumber}</td> | |
| <td class="py-2 px-4 border text-right">${item.description}</td> | |
| <td class="py-2 px-4 border text-center">${item.unit}</td> | |
| <td class="py-2 px-4 border text-center">${item.quantity}</td> | |
| <td class="py-2 px-4 border text-center">${item.unitPrice.toFixed(2)}</td> | |
| <td class="py-2 px-4 border text-center">${item.total.toFixed(2)}</td> | |
| `; | |
| itemsTable.appendChild(row); | |
| }); | |
| // Only show totals and terms on the last page | |
| if (i < pageCount - 1) { | |
| quotePage.querySelector('#quoteTotalsSection').classList.add('hidden'); | |
| quotePage.querySelector('#quoteTermsSection').classList.add('hidden'); | |
| quotePage.querySelector('#quoteNotesSection').classList.add('hidden'); | |
| } else { | |
| // Calculate totals | |
| const subtotal = items.reduce((sum, item) => sum + item.total, 0); | |
| const tax = subtotal * 0.14; | |
| const total = subtotal + tax; | |
| quotePage.querySelector('#quoteSubtotal').textContent = subtotal.toFixed(2); | |
| quotePage.querySelector('#quoteTax').textContent = tax.toFixed(2); | |
| quotePage.querySelector('#quoteTotal').textContent = total.toFixed(2); | |
| // Add notes if available | |
| if (notes) { | |
| quotePage.querySelector('#quoteNotesContent').innerHTML = notes.replace(/\n/g, '<br>'); | |
| } else { | |
| quotePage.querySelector('#quoteNotesSection').classList.add('hidden'); | |
| } | |
| // Add terms if available | |
| if (companySettings.paymentTerms) { | |
| quotePage.querySelector('#quotePaymentTerms').innerHTML = companySettings.paymentTerms.replace(/\n/g, '<br>'); | |
| } else { | |
| quotePage.querySelector('#quotePaymentTerms').parentElement.classList.add('hidden'); | |
| } | |
| if (companySettings.deliveryTerms) { | |
| quotePage.querySelector('#quoteDeliveryTerms').innerHTML = companySettings.deliveryTerms.replace(/\n/g, '<br>'); | |
| } else { | |
| quotePage.querySelector('#quoteDeliveryTerms').parentElement.classList.add('hidden'); | |
| } | |
| if (!companySettings.paymentTerms && !companySettings.deliveryTerms) { | |
| quotePage.querySelector('#quoteTermsSection').classList.add('hidden'); | |
| } | |
| } | |
| // Add the page to the preview | |
| quotePreview.appendChild(quotePage); | |
| } | |
| // Show the preview section | |
| document.getElementById('inputSection').classList.add('hidden'); | |
| document.getElementById('previewSection').classList.remove('hidden'); | |
| } | |
| // Format date from YYYY-MM-DD to DD/MM/YYYY | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| const day = date.getDate().toString().padStart(2, '0'); | |
| const month = (date.getMonth() + 1).toString().padStart(2, '0'); | |
| const year = date.getFullYear(); | |
| return `${day}/${month}/${year}`; | |
| } | |
| // Export to PDF | |
| function exportToPdf() { | |
| alert('تصدير إلى PDF سيتم تنفيذه باستخدام مكتبة jsPDF أو html2pdf.js في التنفيذ الفعلي'); | |
| // In a real implementation, you would use a library like jsPDF or html2pdf.js | |
| } | |
| // Export to Word | |
| function exportToWord() { | |
| alert('تصدير إلى Word سيتم تنفيذه باستخدام مكتبة docx.js في التنفيذ الفعلي'); | |
| // In a real implementation, you would use a library like docx.js | |
| } | |
| // Export to Excel | |
| function exportToExcel() { | |
| alert('تصدير إلى Excel سيتم تنفيذه باستخدام مكتبة sheetjs في التنفيذ الفعلي'); | |
| // In a real implementation, you would use a library like sheetjs | |
| } | |
| // Print the quote | |
| function printQuote() { | |
| window.print(); | |
| } | |
| // Export database | |
| function exportDatabase() { | |
| const data = { | |
| settings: companySettings, | |
| quotes: [] // In a real app, you would store quotes here | |
| }; | |
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'quotation_system_backup.json'; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| // Import database | |
| function importDatabase() { | |
| const input = document.createElement('input'); | |
| input.type = 'file'; | |
| input.accept = '.json'; | |
| input.onchange = e => { | |
| const file = e.target.files[0]; | |
| const reader = new FileReader(); | |
| reader.onload = event => { | |
| try { | |
| const data = JSON.parse(event.target.result); | |
| if (data.settings) { | |
| companySettings = data.settings; | |
| saveSettings(); | |
| updateSettingsForm(); | |
| alert('تم استيراد الإعدادات بنجاح'); | |
| } | |
| } catch (error) { | |
| alert('خطأ في قراءة الملف: ' + error.message); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| }; | |
| input.click(); | |
| } | |
| // Event Listeners | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Load settings | |
| loadSettings(); | |
| // Set default date | |
| const today = new Date(); | |
| const formattedDate = today.toISOString().split('T')[0]; | |
| document.getElementById('quoteDate').value = formattedDate; | |
| // Generate random quote number | |
| document.getElementById('quoteNumber').value = 'Q-' + Math.floor(1000 + Math.random() * 9000); | |
| // Set today's date as default | |
| const today = new Date().toISOString().split('T')[0]; | |
| document.getElementById('quoteDate').value = today; | |
| // Generate a random quote number | |
| document.getElementById('quoteNumber').value = 'Q-' + Math.floor(1000 + Math.random() * 9000); | |
| // Add first empty item | |
| addItem(); | |
| // Add item button | |
| document.getElementById('addItemBtn').addEventListener('click', addItem); | |
| // Generate quote button | |
| document.getElementById('generateQuoteBtn').addEventListener('click', generateQuote); | |
| // Preview button | |
| document.getElementById('previewBtn').addEventListener('click', function() { | |
| if (validateForm()) { | |
| generateQuote(); | |
| } | |
| }); | |
| function validateForm() { | |
| const clientName = document.getElementById('clientName').value; | |
| if (!clientName) { | |
| alert('الرجاء إدخال اسم الجهة'); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Back to input button | |
| document.getElementById('backToInputBtn').addEventListener('click', function() { | |
| document.getElementById('inputSection').classList.remove('hidden'); | |
| document.getElementById('previewSection').classList.add('hidden'); | |
| }); | |
| // Export buttons | |
| document.getElementById('exportPdfBtn').addEventListener('click', exportToPdf); | |
| document.getElementById('exportWordBtn').addEventListener('click', exportToWord); | |
| document.getElementById('exportExcelBtn').addEventListener('click', exportToExcel); | |
| document.getElementById('printBtn').addEventListener('click', printQuote); | |
| // Settings buttons | |
| document.getElementById('settingsBtn').addEventListener('click', function() { | |
| document.getElementById('settingsModal').classList.remove('hidden'); | |
| }); | |
| document.getElementById('closeSettingsBtn').addEventListener('click', function() { | |
| document.getElementById('settingsModal').classList.add('hidden'); | |
| }); | |
| document.getElementById('cancelSettingsBtn').addEventListener('click', function() { | |
| document.getElementById('settingsModal').classList.add('hidden'); | |
| updateSettingsForm(); // Reset form to current settings | |
| }); | |
| document.getElementById('saveSettingsBtn').addEventListener('click', function() { | |
| // Update settings from form | |
| companySettings.name = document.getElementById('companyName').value; | |
| companySettings.address = document.getElementById('companyAddress').value; | |
| companySettings.phone = document.getElementById('companyPhone').value; | |
| companySettings.email = document.getElementById('companyEmail').value; | |
| companySettings.commercialRegister = document.getElementById('companyCommercialRegister').value; | |
| companySettings.taxId = document.getElementById('companyTaxId').value; | |
| companySettings.primaryColor = document.getElementById('primaryColor').value; | |
| companySettings.paymentTerms = document.getElementById('paymentTerms').value; | |
| companySettings.deliveryTerms = document.getElementById('deliveryTerms').value; | |
| companySettings.generalNotes = document.getElementById('generalNotes').value; | |
| saveSettings(); | |
| document.getElementById('settingsModal').classList.add('hidden'); | |
| // Apply primary color | |
| document.documentElement.style.setProperty('--tw-ring-color', companySettings.primaryColor); | |
| }); | |
| // Logo upload | |
| document.getElementById('uploadLogoBtn').addEventListener('click', function() { | |
| document.getElementById('logoUpload').click(); | |
| }); | |
| document.getElementById('logoUpload').addEventListener('change', function(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| companySettings.logo = event.target.result; | |
| document.getElementById('logoPreview').src = companySettings.logo; | |
| document.getElementById('logoPreview').classList.remove('hidden'); | |
| document.getElementById('removeLogoBtn').classList.remove('hidden'); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| document.getElementById('removeLogoBtn').addEventListener('click', function() { | |
| companySettings.logo = ''; | |
| document.getElementById('logoPreview').src = ''; | |
| document.getElementById('logoPreview').classList.add('hidden'); | |
| this.classList.add('hidden'); | |
| }); | |
| // Export/import database | |
| document.getElementById('exportBtn').addEventListener('click', exportDatabase); | |
| document.getElementById('importBtn').addEventListener('click', importDatabase); | |
| // New quote button | |
| document.getElementById('newQuoteBtn').addEventListener('click', function() { | |
| // Clear items | |
| items = []; | |
| document.getElementById('itemsTable').innerHTML = ''; | |
| // Reset form | |
| document.getElementById('clientName').value = ''; | |
| document.getElementById('notes').value = ''; | |
| // Generate new quote number | |
| document.getElementById('quoteNumber').value = 'Q-' + Math.floor(1000 + Math.random() * 9000); | |
| // Add first empty item | |
| addItem(); | |
| // Show input section if not already visible | |
| document.getElementById('inputSection').classList.remove('hidden'); | |
| document.getElementById('previewSection').classList.add('hidden'); | |
| }); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=YousefKhamis/quotes-generator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |