Inventory System

Sign in to continue

Demo Login: 8928100281 / password123

`; printWindow.document.write(html); printWindow.document.close(); // Wait for content to load, then print setTimeout(() => { printWindow.print(); showToast('Print dialog opened'); closeModal(); }, 250); } // ========== EXPORT ========== function showExcelOptions(section) { const modal = document.getElementById('modal-container'); modal.innerHTML = ` `; refreshIcons(); } function triggerImportExcel(section) { const input = document.createElement('input'); input.type = 'file'; input.accept = '.xlsx,.xls'; input.onchange = (e) => importExcel(section, e.target.files[0]); input.click(); } async function importExcel(section, file) { try { if (!file) return; if (typeof XLSX === 'undefined' || !XLSX.read) { showToast('Excel library loading... please try again', 'warning'); return; } const reader = new FileReader(); reader.onload = async (e) => { try { const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, { type: 'array' }); const ws = workbook.Sheets[workbook.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json(ws); if (rows.length === 0) { showToast('No data found in Excel file', 'warning'); return; } const modal = document.getElementById('modal-container'); modal.innerHTML = ` `; refreshIcons(); } catch(err) { console.error('Error processing Excel:', err); showToast('Error reading Excel file', 'error'); } }; reader.readAsArrayBuffer(file); } catch(e) { console.error('Import error:', e); showToast('Import failed. Please try again.', 'error'); } } async function confirmImport(section, rows) { const btn = event.target; btn.disabled = true; btn.textContent = 'Importing...'; try { let imported = 0; let skipped = 0; for (const row of rows) { if (allData.length >= 999) { showToast(`Reached maximum limit. Imported ${imported} records.`, 'warning'); break; } const cleanRow = {}; for (const [key, value] of Object.entries(row)) { const cleanKey = (key || '').trim().toLowerCase().replace(/\s+/g, '_'); if (value !== null && value !== undefined && value !== '') { cleanRow[cleanKey] = typeof value === 'number' ? value : String(value).trim(); } } // Validate and map data if (section === 'parties') { if (!cleanRow.party_name) { skipped++; continue; } await window.dataSdk.create({ type: 'parties', party_name: cleanRow.party_name, contact: cleanRow.contact || '', address: cleanRow.address || '', created_at: new Date().toISOString() }); imported++; } else if (section === 'product' || section === 'raw') { if (!cleanRow.item_code || !cleanRow.item_name) { skipped++; continue; } if (isDuplicateCode(section, cleanRow.item_code) || isDuplicateItemName(section, cleanRow.item_name)) { skipped++; continue; } await window.dataSdk.create({ type: section, item_code: cleanRow.item_code, item_name: cleanRow.item_name, stock_qty: parseFloat(cleanRow.stock_qty) || 0, opening_stock: parseFloat(cleanRow.stock_qty) || 0, low_stock: parseFloat(cleanRow.low_stock || cleanRow.min_stock) || 0, description: cleanRow.description || '', category: section, created_at: new Date().toISOString() }); imported++; } else if (section === 'inward' || section === 'outward') { if (!cleanRow.item_code || !cleanRow.item_name || !cleanRow.stock_qty) { skipped++; continue; } const obj = { type: section, item_code: cleanRow.item_code, item_name: cleanRow.item_name, stock_qty: parseFloat(cleanRow.stock_qty) || 0, date: cleanRow.date ? new Date(cleanRow.date).toISOString() : new Date().toISOString(), description: cleanRow.description || '', category: section, created_at: new Date().toISOString() }; if (section === 'outward') { obj.party_name = cleanRow.party_name || 'Imported'; } await window.dataSdk.create(obj); imported++; } else if (section === 'users') { if (!cleanRow.user_name || !cleanRow.mobile_number || !cleanRow.password) { skipped++; continue; } await window.dataSdk.create({ type: 'users', user_name: cleanRow.user_name, mobile_number: cleanRow.mobile_number, password: cleanRow.password, created_at: new Date().toISOString() }); imported++; } } closeModal(); showToast(`Imported ${imported} records${skipped > 0 ? ` (${skipped} skipped)` : ''}`); renderMain(); } catch(e) { console.error('Import error:', e); showToast('Import failed. Please try again.', 'error'); btn.disabled = false; btn.textContent = 'Import All'; } } function exportExcel(section) { try { const data = getDataByType(section); if (data.length === 0) { showToast('No data to export', 'warning'); return; } if (typeof XLSX === 'undefined' || !XLSX.utils) { showToast('Excel library loading... please try again', 'warning'); return; } const headers = getExportHeaders(section); const rows = data.map((d, i) => getExportRow(section, d, i)); const ws = XLSX.utils.aoa_to_sheet([headers, ...rows]); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, section); // Create filename with proper .xlsx extension const timestamp = new Date().getTime(); const filename = `${section}_${timestamp}.xlsx`; // Write and download file XLSX.writeFile(wb, filename); // Show success after a brief delay setTimeout(() => { showToast('Excel downloaded successfully'); closeModal(); }, 300); } catch(e) { console.error('Excel export error:', e); showToast('Download failed. Please try again.', 'error'); } } function exportCSV(section) { try { const data = getDataByType(section); if (data.length === 0) { showToast('No data to export', 'warning'); return; } const headers = getExportHeaders(section); const rows = data.map((d, i) => getExportRow(section, d, i)); // Convert to CSV format const csvContent = [ headers.map(h => `"${h}"`).join(','), ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) ].join('\n'); // Create blob and download const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const timestamp = new Date().getTime(); const filename = `${section}_${timestamp}.csv`; link.setAttribute('href', URL.createObjectURL(blob)); link.setAttribute('download', filename); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); setTimeout(() => { showToast('CSV downloaded successfully'); closeModal(); }, 300); } catch(e) { console.error('CSV export error:', e); showToast('Download failed. Please try again.', 'error'); } } function exportPDF(section) { try { const data = getDataByType(section); if (data.length === 0) { showToast('No data to export', 'warning'); return; } if (typeof jspdf === 'undefined' || !jspdf.jsPDF) { showToast('PDF library loading... please try again', 'warning'); return; } const jsPDF = jspdf.jsPDF; const doc = new jsPDF(); const headers = getExportHeaders(section); const rows = data.map((d, i) => getExportRow(section, d, i)); doc.setFontSize(16); doc.text(section.charAt(0).toUpperCase() + section.slice(1) + ' Report', 14, 20); doc.setFontSize(10); doc.text(new Date().toLocaleDateString(), 14, 28); if (doc.autoTable) { doc.autoTable({ head: [headers], body: rows, startY: 35, theme: 'grid', styles: { fontSize: 8 }, headStyles: { fillColor: [99, 102, 241] } }); } doc.save(`${section}_${new Date().getTime()}.pdf`); showToast('PDF downloaded successfully'); } catch(e) { console.error('PDF export error:', e); showToast('Download failed. Please try again.', 'error'); } } function exportToCanvaSheet(section) { try { const data = getDataByType(section); if (data.length === 0) { showToast('No data to export', 'warning'); return; } const modal = document.getElementById('modal-container'); modal.innerHTML = ` `; refreshIcons(); showToast('Data is saved in your Canva Sheet'); } catch(e) { console.error('Canva Sheet export error:', e); showToast('Export failed. Please try again.', 'error'); } } function viewCanvaData() { showToast('Click the Data button above to access your Canva Sheet'); closeModal(); } function getExportHeaders(section) { const h = { parties: ['SR', 'Party Name', 'Contact', 'Address', 'Description'], product: ['SR', 'Item Code', 'Item Name', 'Stock QTY', 'Min Stock', 'Status', 'Description'], raw: ['SR', 'Item Code', 'Item Name', 'Stock QTY', 'Min Stock', 'Status', 'Description'], inward: ['SR', 'Date', 'Party Name', 'Item Code', 'Item Name', 'Stock QTY', 'Description'], outward: ['SR', 'Date', 'Party Name', 'Item Code', 'Item Name', 'Stock QTY', 'Description'], users: ['SR', 'User Name', 'Mobile Number', 'Created Date'] }; return h[section] || []; } function getExportRow(section, d, i) { switch(section) { case 'parties': return [parseInt(d.custom_sr, 10) || '', d.party_name||'', d.contact||'', d.address||'', d.description||'']; case 'product': case 'raw': const qty = d.stock_qty || 0; const low = d.low_stock || 0; const isLow = qty <= low && low > 0; const status = isLow ? 'Low' : 'OK'; return [parseInt(d.custom_sr, 10) || '', d.item_code||'', d.item_name||'', qty, low, status, d.description||'']; case 'inward': return [parseInt(d.custom_sr, 10) || '', d.date ? new Date(d.date).toLocaleDateString() : '', d.party_name||'', d.item_code||'', d.item_name||'', d.stock_qty||0, d.description||'']; case 'outward': return [parseInt(d.custom_sr, 10) || '', d.date ? new Date(d.date).toLocaleDateString() : '', d.party_name||'', d.item_code||'', d.item_name||'', d.stock_qty||0, d.description||'']; case 'users': return [parseInt(d.custom_sr, 10) || '', d.user_name||'', d.mobile_number||'', d.created_at ? new Date(d.created_at).toLocaleDateString() : '']; default: return []; } } // ========== MYSQL API DATA SDK ========== (function () { const API_URL = './api/records.php'; async function request(payload = {}, method = 'POST') { const options = { method, headers: { 'Content-Type': 'application/json' } }; if (method !== 'GET') options.body = JSON.stringify(payload || {}); const response = await fetch(API_URL, options); let result = null; try { result = await response.json(); } catch (error) { throw new Error('Invalid server response'); } if (!response.ok || !result?.isOk) { const message = result?.error || `HTTP ${response.status}`; throw new Error(message); } return result; } async function notifyChange() { const result = await request({ action: 'getAll' }); if (window.dataHandler && typeof window.dataHandler.onDataChanged === 'function') { window.dataHandler.onDataChanged(Array.isArray(result.data) ? result.data : []); } } window.dataSdk = { async init(handler) { try { window.dataHandler = handler; await request({ action: 'init' }); await notifyChange(); return { isOk: true }; } catch (error) { return { isOk: false, error: error.message || String(error) }; } }, async getAll() { try { const result = await request({ action: 'getAll' }); return { isOk: true, data: result.data || [] }; } catch (error) { return { isOk: false, error: error.message || String(error) }; } }, async create(record) { try { const result = await request({ action: 'create', record }); await notifyChange(); return { isOk: true, data: result.data }; } catch (error) { return { isOk: false, error: error.message || String(error) }; } }, async update(record) { try { const result = await request({ action: 'update', record }); await notifyChange(); return { isOk: true, data: result.data }; } catch (error) { return { isOk: false, error: error.message || String(error) }; } }, async delete(record) { try { const result = await request({ action: 'delete', record }); await notifyChange(); return { isOk: true, data: result.data }; } catch (error) { return { isOk: false, error: error.message || String(error) }; } }, async clearAll() { try { const result = await request({ action: 'clearAll' }); await notifyChange(); return { isOk: true, data: result.data }; } catch (error) { return { isOk: false, error: error.message || String(error) }; } } }; })(); // ========== DATA HANDLER ========== window.dataHandler = { onDataChanged(data) { allData = Array.isArray(data) ? data : []; if (!currentUser) { showLoginPage(); initLoginUI(); } else { showMainApp(); const footerUserEl = document.getElementById('footer-user-id'); if (footerUserEl) footerUserEl.textContent = currentUser?.mobile_number || defaultConfig.user_id_text; document.getElementById('current-date').textContent = new Date().toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' }); renderMain(); const lowItems = allData.filter(d => (d.type === 'product' || d.type === 'raw') && Number(d.low_stock) > 0 && Number(d.stock_qty) <= Number(d.low_stock) ); const dot = document.getElementById('low-stock-dot'); if (dot) dot.style.display = lowItems.length > 0 ? 'block' : 'none'; } } }; async function ensureTestUser() { const result = await window.dataSdk.getAll(); if (!result.isOk) return; const testUserExists = result.data.some(d => d.type === 'users' && String(d.mobile_number || '').trim() === '8928100281'); if (!testUserExists) { await window.dataSdk.create({ type: 'users', user_name: 'Test User', mobile_number: '8928100281', password: 'password123', created_at: new Date().toISOString() }); } } async function removeSampleInwardRecords() { const sampleParties = new Set(['abc trading', 'xyz supplies', 'demo party', 'sample party', 'test party']); const sampleItems = new Set(['widget a', 'widget b', 'sample item', 'demo item', 'test item']); const result = await window.dataSdk.getAll(); if (!result.isOk || !Array.isArray(result.data)) return; const sampleInwardRecords = result.data.filter((d) => { if (d.type !== 'inward') return false; const party = String(d.party_name || '').trim().toLowerCase(); const item = String(d.item_name || '').trim().toLowerCase(); const id = String(d.__backendId || '').trim().toLowerCase(); return sampleParties.has(party) || sampleItems.has(item) || id.includes('demo') || id.includes('sample') || id.includes('test'); }); for (const record of sampleInwardRecords) { await window.dataSdk.delete(record); } } async function initDataSDK() { const result = await window.dataSdk.init(window.dataHandler); if (!result.isOk) { console.error('Failed to initialize MySQL API:', result.error); loadTestData(); window.dataHandler.onDataChanged(allData); } } async function initApp() { initLoginUI(); await initDataSDK(); await ensureTestUser(); await removeSampleInwardRecords(); await ensureStableSRNumbers(); document.getElementById('header-title').textContent = defaultConfig.app_title; document.getElementById('footer-user-id').textContent = currentUser?.mobile_number || defaultConfig.user_id_text; document.body.style.backgroundColor = defaultConfig.background_color; document.body.style.color = defaultConfig.text_color; document.getElementById('current-date').textContent = new Date().toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' }); requestAnimationFrame(() => renderMain()); } // Save data to Google Sheets async function saveToGoogleSheet(section) { showToast('This MySQL build stores data in your local MySQL database. Google Sheets sync is disabled.', 'warning'); } // Load sample data (fallback) function loadTestData() { const testData = [ { type: 'parties', custom_sr: '1', party_name: 'ABC Trading', contact: '9876543210', address: 'Mumbai, India', __backendId: generateId(), created_at: new Date().toISOString() }, { type: 'parties', custom_sr: '2', party_name: 'XYZ Supplies', contact: '9876543211', address: 'Delhi, India', __backendId: generateId(), created_at: new Date().toISOString() }, { type: 'product', custom_sr: '1', item_code: 'PRD-001', item_name: 'Widget A', stock_qty: 150, opening_stock: 150, low_stock: 50, category: 'product', __backendId: generateId(), created_at: new Date().toISOString() }, { type: 'product', custom_sr: '2', item_code: 'PRD-002', item_name: 'Widget B', stock_qty: 200, opening_stock: 200, low_stock: 75, category: 'product', __backendId: generateId(), created_at: new Date().toISOString() }, { type: 'raw', custom_sr: '1', item_code: 'RAW-001', item_name: 'Steel Sheet', stock_qty: 500, opening_stock: 500, low_stock: 100, category: 'raw', __backendId: generateId(), created_at: new Date().toISOString() }, { type: 'raw', custom_sr: '2', item_code: 'RAW-002', item_name: 'Aluminum Coil', stock_qty: 300, opening_stock: 300, low_stock: 80, category: 'raw', __backendId: generateId(), created_at: new Date().toISOString() }, ]; allData = testData; } initApp(); refreshIcons();