GooseMkz/suppliersync-pro-dashboard
0
1 2document.addEventListener('DOMContentLoaded', function() {3 // Handle sidebar collapse4 document.addEventListener('sidebarCollapse', (e) => {5 if (e.detail.collapsed) {6 document.body.classList.add('sidebar-collapsed');7 } else {8 document.body.classList.remove('sidebar-collapsed');9 }10 });11 12// Initialize Feather Icons13 feather.replace();14 15 // Mock data for suppliers16 const suppliers = [17 {18 id: 1,19 companyName: "ООО 'ТехноПром'",20 inn: 1234567890,21 warehouseAddress: "г. Москва, ул. Промышленная, д. 42",22 contactPerson: "Иванов Сергей Петрович",23 phone: "+7 (495) 123-45-67",24 email: "info@technoprom.ru",25 utzManager: "Иванов И.И.",26 productManager: "Смирнова А.В.",27 status: "active",28 date: "2023-05-15"29 },30 {31 id: 2,32 companyName: "АО 'СтройМатериалы'",33 inn: 9876543210,34 warehouseAddress: "г. Санкт-Петербург, пр. Ленина, д. 12",35 contactPerson: "Петрова Мария Ивановна",36 phone: "+7 (812) 987-65-43",37 email: "contact@stroymat.ru",38 utzManager: "Петров П.П.",39 productManager: "Кузнецова Е.П.",40 status: "active",41 date: "2023-06-20"42 },43 {44 id: 3,45 companyName: "ЗАО 'Электрон'",46 inn: 4567890123,47 warehouseAddress: "г. Новосибирск, ул. Электронная, д. 5",48 contactPerson: "Сидоров Алексей Викторович",49 phone: "+7 (383) 456-78-90",50 email: "sales@electron.ru",51 utzManager: "Сидоров С.С.",52 productManager: "Васильева О.М.",53 status: "inactive",54 date: "2023-04-10"55 },56 {57 id: 4,58 companyName: "ООО 'АгроТех'",59 inn: 3456789012,60 warehouseAddress: "г. Казань, ул. Аграрная, д. 15",61 contactPerson: "Галимов Рамиль Фаритович",62 phone: "+7 (843) 345-67-89",63 email: "office@agrotech.ru",64 utzManager: "Иванов И.И.",65 productManager: "Кузнецова Е.П.",66 status: "active",67 date: "2023-07-05"68 },69 {70 id: 5,71 companyName: "ПАО 'МеталлТрейд'",72 inn: 5678901234,73 warehouseAddress: "г. Екатеринбург, ул. Металлургов, д. 24",74 contactPerson: "Кузнецов Дмитрий Сергеевич",75 phone: "+7 (343) 567-89-01",76 email: "metall@metalltrade.ru",77 utzManager: "Петров П.П.",78 productManager: "Смирнова А.В.",79 status: "active",80 date: "2023-08-12"81 },82 {83 id: 6,84 companyName: "ООО 'ТекстильГрупп'",85 inn: 6789012345,86 warehouseAddress: "г. Ростов-на-Дону, ул. Текстильная, д. 8",87 contactPerson: "Волкова Ольга Николаевна",88 phone: "+7 (863) 678-90-12",89 email: "textile@textilegroup.ru",90 utzManager: "Сидоров С.С.",91 productManager: "Васильева О.М.",92 status: "inactive",93 date: "2023-03-18"94 },95 {96 id: 7,97 companyName: "АО 'ХимПром'",98 inn: 7890123456,99 warehouseAddress: "г. Нижний Новгород, ул. Химиков, д. 3",100 contactPerson: "Николаев Андрей Владимирович",101 phone: "+7 (831) 789-01-23",102 email: "info@khimprom.ru",103 utzManager: "Иванов И.И.",104 productManager: "Кузнецова Е.П.",105 status: "active",106 date: "2023-09-22"107 },108 {109 id: 8,110 companyName: "ООО 'СтройЭксперт'",111 inn: 8901234567,112 warehouseAddress: "г. Самара, ул. Строителей, д. 17",113 contactPerson: "Федоров Михаил Александрович",114 phone: "+7 (846) 890-12-34",115 email: "contact@stroiexpert.ru",116 utzManager: "Петров П.П.",117 productManager: "Смирнова А.В.",118 status: "active",119 date: "2023-10-15"120 },121 {122 id: 9,123 companyName: "ЗАО 'АвтоДеталь'",124 inn: 9012345678,125 warehouseAddress: "г. Уфа, ул. Автозаводская, д. 9",126 contactPerson: "Гареев Артур Рамилевич",127 phone: "+7 (347) 901-23-45",128 email: "sales@autodetal.ru",129 utzManager: "Сидоров С.С.",130 productManager: "Васильева О.М.",131 status: "active",132 date: "2023-11-05"133 },134 {135 id: 10,136 companyName: "ООО 'ПищеПром'",137 inn: 1122334455,138 warehouseAddress: "г. Краснодар, ул. Пищевая, д. 21",139 contactPerson: "Семенова Елена Викторовна",140 phone: "+7 (861) 112-23-44",141 email: "office@pisheprom.ru",142 utzManager: "Иванов И.И.",143 productManager: "Кузнецова Е.П.",144 status: "inactive",145 date: "2023-02-28"146 }147 ];148 149 // DOM Elements150 const suppliersTableBody = document.getElementById('suppliersTableBody');151 const addSupplierBtn = document.getElementById('addSupplierBtn');152 const exportBtn = document.getElementById('exportBtn');153 const addSupplierModal = document.getElementById('addSupplierModal');154 const deleteModal = document.getElementById('deleteModal');155 const searchInput = document.getElementById('searchInput');156 const statusFilter = document.getElementById('statusFilter');157 const managerFilter = document.getElementById('managerFilter');158 const supplierForm = document.getElementById('supplierForm');159 const saveSupplierBtn = document.getElementById('saveSupplierBtn');160 const cancelSupplierBtn = document.getElementById('cancelSupplierBtn');161 const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');162 const cancelDeleteBtn = document.getElementById('cancelDeleteBtn');163 const deleteModalText = document.getElementById('deleteModalText');164 const paginationInfo = document.getElementById('paginationInfo');165 const pageNumbers = document.getElementById('pageNumbers');166 const prevPage = document.getElementById('prevPage');167 const nextPage = document.getElementById('nextPage');168 const prevPageMobile = document.getElementById('prevPageMobile');169 const nextPageMobile = document.getElementById('nextPageMobile');170 const toastContainer = document.getElementById('toastContainer');171 172 // Variables173 let currentSuppliers = [...suppliers];174 let currentPage = 1;175 const itemsPerPage = 5;176 let sortColumn = null;177 let sortDirection = 'asc';178 let supplierToDeleteId = null;179 let isEditing = false;180 let currentEditId = null;181 182 // Initialize the table183 function initTable() {184 renderSuppliers();185 setupPagination();186 setupSorting();187 }188 189 // Render suppliers to the table190 function renderSuppliers() {191 suppliersTableBody.innerHTML = '';192 193 const filteredSuppliers = filterSuppliers();194 const paginatedSuppliers = paginateSuppliers(filteredSuppliers);195 196 paginatedSuppliers.forEach(supplier => {197 const row = document.createElement('tr');198 row.className = 'hover:bg-gray-50 transition';199 row.innerHTML = `200 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${supplier.companyName}</td>201 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${supplier.inn}</td>202 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${supplier.warehouseAddress}</td>203 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${supplier.contactPerson}</td>204 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${supplier.phone}</td>205 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${supplier.email}</td>206 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${supplier.utzManager}</td>207 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${supplier.productManager}</td>208 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">209 <span class="badge ${supplier.status === 'active' ? 'badge-active' : 'badge-inactive'}">210 ${supplier.status === 'active' ? 'Активный' : 'Неактивный'}211 </span>212 </td>213 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formatDate(supplier.date)}</td>214 <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">215 <button class="text-blue-600 hover:text-blue-900 mr-3 edit-btn" data-id="${supplier.id}">216 <i data-feather="edit" class="w-4 h-4"></i>217 </button>218 <button class="text-red-600 hover:text-red-900 delete-btn" data-id="${supplier.id}">219 <i data-feather="trash-2" class="w-4 h-4"></i>220 </button>221 </td>222 `;223 suppliersTableBody.appendChild(row);224 });225 226 // Update pagination info227 const filteredCount = filteredSuppliers.length;228 const startItem = (currentPage - 1) * itemsPerPage + 1;229 const endItem = Math.min(currentPage * itemsPerPage, filteredCount);230 231 paginationInfo.innerHTML = `232 Показано <span class="font-medium">${startItem}</span> - <span class="font-medium">${endItem}</span> из <span class="font-medium">${filteredCount}</span> поставщиков233 `;234 235 // Refresh feather icons236 feather.replace();237 238 // Add event listeners to edit and delete buttons239 document.querySelectorAll('.edit-btn').forEach(btn => {240 btn.addEventListener('click', (e) => {241 const id = parseInt(e.currentTarget.getAttribute('data-id'));242 editSupplier(id);243 });244 });245 246 document.querySelectorAll('.delete-btn').forEach(btn => {247 btn.addEventListener('click', (e) => {248 const id = parseInt(e.currentTarget.getAttribute('data-id'));249 confirmDelete(id);250 });251 });252 }253 254 // Filter suppliers based on search and filters255 function filterSuppliers() {256 let filtered = [...suppliers];257 258 // Search filter259 const searchTerm = searchInput.value.toLowerCase();260 if (searchTerm) {261 filtered = filtered.filter(supplier => 262 supplier.companyName.toLowerCase().includes(searchTerm) ||263 supplier.inn.toString().includes(searchTerm) ||264 supplier.email.toLowerCase().includes(searchTerm)265 );266 }267 268 // Status filter269 const statusFilterValue = statusFilter.value;270 if (statusFilterValue) {271 filtered = filtered.filter(supplier => supplier.status === statusFilterValue);272 }273 274 // Manager filter275 const managerFilterValue = managerFilter.value;276 if (managerFilterValue) {277 filtered = filtered.filter(supplier => 278 supplier.utzManager === managerFilterValue || 279 supplier.productManager === managerFilterValue280 );281 }282 283 // Sorting284 if (sortColumn) {285 filtered.sort((a, b) => {286 let valueA, valueB;287 288 if (sortColumn === 'name') {289 valueA = a.companyName;290 valueB = b.companyName;291 } else if (sortColumn === 'inn') {292 valueA = a.inn;293 valueB = b.inn;294 } else if (sortColumn === 'date') {295 valueA = new Date(a.date);296 valueB = new Date(b.date);297 }298 299 if (valueA < valueB) {300 return sortDirection === 'asc' ? -1 : 1;301 }302 if (valueA > valueB) {303 return sortDirection === 'asc' ? 1 : -1;304 }305 return 0;306 });307 }308 309 return filtered;310 }311 312 // Paginate suppliers313 function paginateSuppliers(suppliersList) {314 const startIndex = (currentPage - 1) * itemsPerPage;315 return suppliersList.slice(startIndex, startIndex + itemsPerPage);316 }317 318 // Setup pagination319 function setupPagination() {320 const filteredSuppliers = filterSuppliers();321 const totalPages = Math.ceil(filteredSuppliers.length / itemsPerPage);322 323 pageNumbers.innerHTML = '';324 325 for (let i = 1; i <= totalPages; i++) {326 const pageItem = document.createElement('span');327 pageItem.innerHTML = `328 <button class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium ${currentPage === i ? 'text-white bg-blue-600 border-blue-600' : 'text-gray-700 hover:bg-gray-50'} page-btn" data-page="${i}">329 ${i}330 </button>331 `;332 pageNumbers.appendChild(pageItem);333 }334 335 // Add event listeners to page buttons336 document.querySelectorAll('.page-btn').forEach(btn => {337 btn.addEventListener('click', (e) => {338 const page = parseInt(e.currentTarget.getAttribute('data-page'));339 currentPage = page;340 renderSuppliers();341 });342 });343 }344 345 // Setup sorting346 function setupSorting() {347 document.querySelectorAll('[data-sort]').forEach(header => {348 header.addEventListener('click', (e) => {349 const column = e.currentTarget.getAttribute('data-sort');350 351 // Reset all sort icons352 document.querySelectorAll('.sort-icon').forEach(icon => {353 icon.classList.add('hidden');354 });355 356 if (sortColumn === column) {357 // Toggle direction if same column358 sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';359 } else {360 // New column, default to ascending361 sortColumn = column;362 sortDirection = 'asc';363 }364 365 // Show the correct sort icon366 const icons = e.currentTarget.querySelectorAll('.sort-icon');367 if (sortDirection === 'asc') {368 icons[0].classList.remove('hidden');369 } else {370 icons[1].classList.remove('hidden');371 }372 373 // Reset to first page and render374 currentPage = 1;375 renderSuppliers();376 });377 });378 }379 380 // Format date for display381 function formatDate(dateString) {382 const options = { year: 'numeric', month: 'short', day: 'numeric' };383 return new Date(dateString).toLocaleDateString('ru-RU', options);384 }385 386 // Show add supplier modal387 function showAddSupplierModal() {388 isEditing = false;389 document.getElementById('modalTitle').textContent = 'Добавить поставщика';390 resetSupplierForm();391 addSupplierModal.classList.remove('hidden');392 393 // Set focus to first input394 setTimeout(() => {395 document.getElementById('companyName').focus();396 }, 100);397 }398 399 // Show edit supplier modal400 function editSupplier(id) {401 const supplier = suppliers.find(s => s.id === id);402 if (!supplier) return;403 404 isEditing = true;405 currentEditId = id;406 document.getElementById('modalTitle').textContent = 'Редактировать поставщика';407 408 // Fill the form409 document.getElementById('companyName').value = supplier.companyName;410 document.getElementById('inn').value = supplier.inn;411 document.getElementById('warehouseAddress').value = supplier.warehouseAddress;412 document.getElementById('contactPerson').value = supplier.contactPerson;413 document.getElementById('phone').value = supplier.phone;414 document.getElementById('email').value = supplier.email;415 document.getElementById('utzManager').value = supplier.utzManager;416 document.getElementById('productManager').value = supplier.productManager;417 document.getElementById('status').value = supplier.status;418 419 addSupplierModal.classList.remove('hidden');420 421 // Set focus to first input422 setTimeout(() => {423 document.getElementById('companyName').focus();424 }, 100);425 }426 427 // Reset supplier form428 function resetSupplierForm() {429 supplierForm.reset();430 // Clear any validation errors431 document.querySelectorAll('.error-message').forEach(el => el.remove());432 document.querySelectorAll('.input-error').forEach(el => {433 el.classList.remove('input-error');434 });435 }436 437 // Save supplier (add or edit)438 function saveSupplier() {439 // Validate form440 const companyName = document.getElementById('companyName').value.trim();441 const inn = document.getElementById('inn').value.trim();442 const warehouseAddress = document.getElementById('warehouseAddress').value.trim();443 444 let isValid = true;445 446 // Clear previous errors447 document.querySelectorAll('.error-message').forEach(el => el.remove());448 document.querySelectorAll('.input-error').forEach(el => {449 el.classList.remove('input-error');450 });451 452 // Validate required fields453 if (!companyName) {454 showFieldError('companyName', 'Название компании обязательно');455 isValid = false;456 }457 458 if (!inn) {459 showFieldError('inn', 'ИНН обязателен');460 isValid = false;461 } else if (!/^\d{10}$/.test(inn)) {462 showFieldError('inn', 'ИНН должен состоять из 10 цифр');463 isValid = false;464 }465 466 if (!warehouseAddress) {467 showFieldError('warehouseAddress', 'Адрес склада обязателен');468 isValid = false;469 }470 471 if (!isValid) return;472 473 // Create supplier object474 const supplier = {475 companyName,476 inn: parseInt(inn),477 warehouseAddress,478 contactPerson: document.getElementById('contactPerson').value.trim(),479 phone: document.getElementById('phone').value.trim(),480 email: document.getElementById('email').value.trim(),481 utzManager: document.getElementById('utzManager').value,482 productManager: document.getElementById('productManager').value,483 status: document.getElementById('status').value,484 date: new Date().toISOString().split('T')[0] // Today's date485 };486 487 if (isEditing) {488 // Update existing supplier489 const index = suppliers.findIndex(s => s.id === currentEditId);490 if (index !== -1) {491 // Keep the original ID and date492 supplier.id = currentEditId;493 supplier.date = suppliers[index].date;494 suppliers[index] = supplier;495 showToast('Изменения сохранены', 'success');496 }497 } else {498 // Add new supplier499 const newId = suppliers.length > 0 ? Math.max(...suppliers.map(s => s.id)) + 1 : 1;500 supplier.id = newId;501 suppliers.push(supplier);502 showToast('Поставщик успешно добавлен', 'success');503 }504 505 // Close modal and refresh table506 addSupplierModal.classList.add('hidden');507 currentPage = 1; // Reset to first page508 renderSuppliers();509 }510 511 // Show field error512 function showFieldError(fieldId, message) {513 const field = document.getElementById(fieldId);514 field.classList.add('input-error');515 516 const errorElement = document.createElement('p');517 errorElement.className = 'error-message';518 errorElement.textContent = message;519 520 field.parentNode.appendChild(errorElement);521 }522 523 // Confirm delete supplier524 function confirmDelete(id) {525 const supplier = suppliers.find(s => s.id === id);526 if (!supplier) return;527 528 supplierToDeleteId = id;529 deleteModalText.textContent = `Вы уверены, что хотите удалить поставщика "${supplier.companyName}"? Это действие нельзя отменить.`;530 deleteModal.classList.remove('hidden');531 }532 533 // Delete supplier534 function deleteSupplier() {535 const index = suppliers.findIndex(s => s.id === supplierToDeleteId);536 if (index !== -1) {537 const supplierName = suppliers[index].companyName;538 suppliers.splice(index, 1);539 showToast(`Поставщик "${supplierName}" удален`, 'error');540 541 // Reset to first page if the last item on the current page was deleted542 const filteredSuppliers = filterSuppliers();543 const totalPages = Math.ceil(filteredSuppliers.length / itemsPerPage);544 if (currentPage > totalPages) {545 currentPage = Math.max(1, totalPages);546 }547 548 renderSuppliers();549 }550 551 deleteModal.classList.add('hidden');552 supplierToDeleteId = null;553 }554 555 // Show toast notification556 function showToast(message, type) {557 const toast = document.createElement('div');558 toast.className = `toast toast-${type}`;559 560 const icon = type === 'success' ? 'check-circle' : 'alert-circle';561 562 toast.innerHTML = `563 <i data-feather="${icon}" class="toast-icon"></i>564 <span>${message}</span>565 <button class="toast-close">566 <i data-feather="x"></i>567 </button>568 `;569 570 toastContainer.appendChild(toast);571 feather.replace();572 573 // Auto remove after 3 seconds574 setTimeout(() => {575 toast.remove();576 }, 3000);577 578 // Close button579 toast.querySelector('.toast-close').addEventListener('click', () => {580 toast.remove();581 });582 }583 584 // Export to Excel585 function exportToExcel() {586 const filteredSuppliers = filterSuppliers();587 588 // Prepare data for export589 const data = filteredSuppliers.map(supplier => ({590 'Название компании': supplier.companyName,591 'ИНН': supplier.inn,592 'Адрес склада': supplier.warehouseAddress,593 'Контактное лицо': supplier.contactPerson,594 'Телефон': supplier.phone,595 'Email': supplier.email,596 'Менеджер УТЗ': supplier.utzManager,597 'Товарный менеджер': supplier.productManager,598 'Статус': supplier.status === 'active' ? 'Активный' : 'Неактивный',599 'Дата добавления': formatDate(supplier.date)600 }));601 602 // Create worksheet603 const ws = XLSX.utils.json_to_sheet(data);604 605 // Create workbook606 const wb = XLSX.utils.book_new();607 XLSX.utils.book_append_sheet(wb, ws, "Поставщики");608 609 // Generate filename with current date610 const today = new Date();611 const dateStr = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`;612 const filename = `Поставщики_${dateStr}.xlsx`;613 614 // Export to Excel615 XLSX.writeFile(wb, filename);616 617 showToast('Экспорт в Excel выполнен', 'success');618 }619 620 // Event Listeners621 addSupplierBtn.addEventListener('click', showAddSupplierModal);622 exportBtn.addEventListener('click', exportToExcel);623 saveSupplierBtn.addEventListener('click', saveSupplier);624 cancelSupplierBtn.addEventListener('click', () => {625 addSupplierModal.classList.add('hidden');626 });627 confirmDeleteBtn.addEventListener('click', deleteSupplier);628 cancelDeleteBtn.addEventListener('click', () => {629 deleteModal.classList.add('hidden');630 supplierToDeleteId = null;631 });632 633 // Filters and search634 [searchInput, statusFilter, managerFilter].forEach(element => {635 element.addEventListener('change', () => {636 currentPage = 1;637 renderSuppliers();638 });639 });640 641 // Pagination navigation642 prevPage.addEventListener('click', () => {643 if (currentPage > 1) {644 currentPage--;645 renderSuppliers();646 }647 });648 649 nextPage.addEventListener('click', () => {650 const filteredSuppliers = filterSuppliers();651 const totalPages = Math.ceil(filteredSuppliers.length / itemsPerPage);652 653 if (currentPage < totalPages) {654 currentPage++;655 renderSuppliers();656 }657 });658 659 prevPageMobile.addEventListener('click', () => {660 if (currentPage > 1) {661 currentPage--;662 renderSuppliers();663 }664 });665 666 nextPageMobile.addEventListener('click', () => {667 const filteredSuppliers = filterSuppliers();668 const totalPages = Math.ceil(filteredSuppliers.length / itemsPerPage);669 670 if (currentPage < totalPages) {671 currentPage++;672 renderSuppliers();673 }674 });675 676 // Close modals when clicking outside677 [addSupplierModal, deleteModal].forEach(modal => {678 modal.addEventListener('click', (e) => {679 if (e.target === modal) {680 modal.classList.add('hidden');681 if (modal === deleteModal) {682 supplierToDeleteId = null;683 }684 }685 });686 });687 688 // Close modals with Escape key689 document.addEventListener('keydown', (e) => {690 if (e.key === 'Escape') {691 if (!addSupplierModal.classList.contains('hidden')) {692 addSupplierModal.classList.add('hidden');693 }694 if (!deleteModal.classList.contains('hidden')) {695 deleteModal.classList.add('hidden');696 supplierToDeleteId = null;697 }698 }699 });700 701 // Initialize the table702 initTable();703});