const { createApp } = Vue; const api = { auds: "/auditories/", oboruds: (audId) => `/oboruds/?aud_id=${encodeURIComponent(audId)}`, allOboruds: "/oboruds/?sort_by_inv=true", owners: "/owners/", zametki: "/zametki/", }; async function fetchJSON(url) { const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } createApp({ data() { return { view: 'byAud', auditories: [], selectedAudId: '', oboruds: [], allOboruds: [], unassignedOboruds: [], totalOboruds: 0, status: '', error: '', printTitle: '', // auth/user management token: '', role: '', users: [], newAdminUsername: '', newAdminPassword: '', newAudName: '', owners: [], newOwnerName: '', zametki: [], newZametkaText: '', equipmentTypes: [], newTypeName: '', newEquipment: { invNumber: null, nazvanie: '', raspologenie: '', kolichestvo: 1, aud_id: '', type_id: '', owner_id: '' }, // Inspection activeInspection: null, inspectionAudId: '', scannedBarcode: '', lastScanResult: null, inspectionStats: { total_checked: 0, total_expected: 0, total_unknown: 0, progress_percent: 0 }, checkedEquipment: [], unknownBarcodes: [], inspectionHistory: [] }; }, computed: { isAuth() { return !!this.token; }, isAdmin() { return this.role === 'admin'; }, isEditor() { return this.role === 'editor'; }, canEdit() { return this.isAdmin || this.isEditor; }, }, methods: { logout() { try { localStorage.removeItem('access_token'); localStorage.removeItem('role'); } catch {} this.token = ''; this.role = ''; this.users = []; this.status = ''; this.error = ''; this.view = 'byAud'; // опционально: редирект на страницу логина // window.location.href = '/login'; }, authHeaders() { const h = {}; if (this.token) h['Authorization'] = `Bearer ${this.token}`; return h; }, async fetchAuth(url, options = {}) { const opt = { ...options, headers: { ...(options.headers||{}), ...this.authHeaders() } }; const res = await fetch(url, opt); if (!res.ok) { const text = await res.text(); throw new Error(`HTTP ${res.status}: ${text}`); } return res.json(); }, async loadAuditories() { this.status = 'Загрузка аудиторий…'; this.error = ''; try { this.auditories = await fetchJSON(api.auds); this.status = ''; } catch (e) { console.error(e); this.error = 'Не удалось загрузить аудитории'; this.status = ''; } }, async loadOboruds() { if (!this.selectedAudId) { this.error = ''; this.status = 'Выберите аудиторию'; return; } this.status = 'Загрузка оборудования…'; this.error = ''; try { this.oboruds = await fetchJSON(api.oboruds(this.selectedAudId)); // init selected owner helper field this.oboruds.forEach(o => { o.selectedOwnerId = o.owner?.id || ''; }); this.status = ''; } catch (e) { console.error(e); this.error = 'Не удалось загрузить оборудование'; this.status = ''; } }, async loadAllOboruds() { this.status = 'Загрузка всего оборудования…'; this.error = ''; try { this.allOboruds = await fetchJSON(api.allOboruds); this.status = `Загружено ${this.allOboruds.length} записей`; } catch (e) { console.error(e); this.error = 'Не удалось загрузить оборудование'; this.status = ''; } }, async showAllEquipment() { this.view = 'allEquipment'; await this.loadAllOboruds(); }, async loadUnassigned() { this.status = 'Загрузка нераспределённого оборудования…'; this.error = ''; try { const [oboruds, stats] = await Promise.all([ fetchJSON('/oboruds/?unassigned=true&sort_by_inv=true'), fetchJSON('/oboruds/stats') ]); this.unassignedOboruds = oboruds; this.totalOboruds = stats.total; this.status = ''; } catch (e) { console.error(e); this.error = 'Не удалось загрузить данные'; this.status = ''; } }, async showUnassigned() { this.view = 'unassigned'; await this.loadUnassigned(); }, getAuditoryName(audId) { if (!audId) return ''; const aud = this.auditories.find(a => a.id === audId); return aud ? aud.audnazvanie : ''; }, printPage() { const aud = this.auditories.find(a => a.id === this.selectedAudId); this.printTitle = 'Аудитория № ' + (aud ? aud.audnazvanie : ''); this.$nextTick(() => { window.print(); }); }, printAllEquipment() { window.print(); }, async loadZametki() { this.status = 'Загрузка заметок…'; this.error = ''; try { this.zametki = await fetchJSON(api.zametki); this.status = ''; } catch (e) { console.error(e); this.error = 'Не удалось загрузить заметки'; this.status = ''; } }, async showZametki() { this.view = 'zametki'; await this.loadZametki(); }, async createZametka() { if (!this.newZametkaText.trim()) { this.status = 'Введите текст заметки'; return; } try { this.status = 'Добавление заметки…'; await this.fetchAuth(api.zametki, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ txtzam: this.newZametkaText }), }); this.newZametkaText = ''; await this.loadZametki(); this.status = 'Заметка добавлена'; } catch (e) { console.error(e); this.error = 'Не удалось добавить заметку'; this.status = ''; } }, async resolveZametka(id) { try { this.status = 'Отметка заметки как решённой…'; await this.fetchAuth(`/zametki/${id}/resolve`, { method: 'PATCH' }); await this.loadZametki(); this.status = 'Заметка отмечена как решённая'; } catch (e) { console.error(e); this.error = 'Не удалось отметить заметку'; this.status = ''; } }, formatDate(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); return d.toLocaleString('ru-RU'); }, async loadEquipmentTypes() { try { this.equipmentTypes = await fetchJSON('/equipment-types/'); } catch (e) { console.error(e); } }, async createEquipmentType() { if (!this.newTypeName.trim()) { this.status = 'Введите название типа'; return; } try { this.status = 'Добавление типа…'; await this.fetchAuth('/equipment-types/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: this.newTypeName }), }); this.newTypeName = ''; await this.loadEquipmentTypes(); this.status = 'Тип добавлен'; } catch (e) { console.error(e); this.error = 'Не удалось добавить тип'; this.status = ''; } }, async createEquipment() { if (!this.newEquipment.nazvanie.trim()) { this.status = 'Введите название оборудования'; return; } try { this.status = 'Добавление оборудования…'; const data = { nazvanie: this.newEquipment.nazvanie, invNumber: this.newEquipment.invNumber || null, raspologenie: this.newEquipment.raspologenie || null, kolichestvo: this.newEquipment.kolichestvo || null, aud_id: this.newEquipment.aud_id || null, type_id: this.newEquipment.type_id || null, owner_id: this.newEquipment.owner_id || null, }; await this.fetchAuth('/oboruds/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); this.newEquipment = { invNumber: null, nazvanie: '', raspologenie: '', kolichestvo: 1, aud_id: '', type_id: '', owner_id: '' }; this.status = 'Оборудование добавлено'; } catch (e) { console.error(e); this.error = 'Не удалось добавить оборудование'; this.status = ''; } }, async saveOwner(item) { try { this.status = 'Сохранение владельца…'; await this.fetchAuth(`/oboruds/${item.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ owner_id: item.selectedOwnerId || null }), }); this.status = 'Сохранено'; } catch (e) { console.error(e); this.error = 'Не удалось сохранить владельца'; this.status = ''; } }, async loadOwners() { try { this.status = this.status || 'Загрузка владельцев…'; const data = await fetchJSON(api.owners); this.owners = data; this.status = ''; } catch (e) { console.error(e); this.error = 'Не удалось загрузить владельцев'; this.status = ''; } }, async createOwner() { if (!this.newOwnerName) { this.status = 'Укажите имя владельца'; return; } try { this.status = 'Добавление владельца…'; await this.fetchAuth(api.owners, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: this.newOwnerName }), }); this.newOwnerName = ''; await this.loadOwners(); // refresh table owner names if visible if (this.selectedAudId) await this.loadOboruds(); this.status = 'Владелец добавлен'; } catch (e) { console.error(e); this.error = 'Не удалось добавить владельца'; this.status = ''; } }, async loadUsers() { try { this.status = 'Загрузка пользователей…'; this.error = ''; this.users = await this.fetchAuth('/auth/users'); this.status = ''; } catch (e) { console.error(e); this.error = 'Не удалось загрузить пользователей'; this.status = ''; } }, async createAdmin() { if (!this.newAdminUsername || !this.newAdminPassword) { this.status = 'Укажите логин и пароль'; return; } try { this.status = 'Создание администратора…'; this.error = ''; await this.fetchAuth('/auth/users/admin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: this.newAdminUsername, password: this.newAdminPassword }), }); this.newAdminUsername = ''; this.newAdminPassword = ''; await this.loadUsers(); this.status = 'Администратор создан'; } catch (e) { console.error(e); this.error = 'Не удалось создать администратора'; this.status = ''; } }, async createAuditory() { if (!this.newAudName) { this.status = 'Укажите название аудитории'; return; } try { this.status = 'Добавление аудитории…'; this.error = ''; await this.fetchAuth('/auditories/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ audnazvanie: this.newAudName }), }); this.newAudName = ''; await this.loadAuditories(); this.status = 'Аудитория добавлена'; } catch (e) { console.error(e); this.error = 'Не удалось добавить аудиторию'; this.status = ''; } }, // Inspection methods async showInspection() { this.view = 'inspection'; this.status = ''; this.error = ''; }, async startInspection() { try { this.status = 'Начало проверки…'; this.error = ''; const response = await this.fetchAuth('/inspections/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ aud_id: this.inspectionAudId || null }) }); this.activeInspection = response; await this.loadInspectionStats(); this.status = 'Проверка начата'; // Фокус на поле ввода this.$nextTick(() => { if (this.$refs.barcodeInput) { this.$refs.barcodeInput.focus(); } }); } catch (e) { console.error(e); this.error = 'Не удалось начать проверку: ' + e.message; this.status = ''; } }, async checkBarcode() { if (!this.scannedBarcode.trim()) return; try { this.status = 'Проверка штрихкода…'; const response = await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}/check`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inv_number: this.scannedBarcode }) }); this.lastScanResult = response; // Очистить поле и обновить данные this.scannedBarcode = ''; await this.loadInspectionStats(); await this.loadInspectionDetails(); this.status = ''; // Автоматически скрыть уведомление через 3 секунды setTimeout(() => { this.lastScanResult = null; }, 3000); // Вернуть фокус на поле ввода this.$nextTick(() => { if (this.$refs.barcodeInput) { this.$refs.barcodeInput.focus(); } }); } catch (e) { console.error(e); this.error = 'Не удалось проверить штрихкод: ' + e.message; this.status = ''; } }, async loadInspectionStats() { try { const response = await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}`); this.inspectionStats = response; } catch (e) { console.error(e); this.error = 'Не удалось загрузить статистику'; } }, async loadInspectionDetails() { try { const response = await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}/records`); this.checkedEquipment = response.records; this.unknownBarcodes = response.unknown_barcodes; } catch (e) { console.error(e); this.error = 'Не удалось загрузить детали проверки'; } }, async refreshInspectionData() { this.status = 'Обновление данных…'; await this.loadInspectionStats(); await this.loadInspectionDetails(); this.status = 'Данные обновлены'; }, async completeInspection() { if (!confirm('Завершить проверку?')) return; try { this.status = 'Завершение проверки…'; await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}/complete`, { method: 'POST' }); this.activeInspection = null; this.inspectionStats = { total_checked: 0, total_expected: 0, total_unknown: 0, progress_percent: 0 }; this.checkedEquipment = []; this.unknownBarcodes = []; this.lastScanResult = null; this.status = 'Проверка завершена'; } catch (e) { console.error(e); this.error = 'Не удалось завершить проверку: ' + e.message; this.status = ''; } }, cancelInspection() { if (confirm('Прервать проверку без сохранения?')) { this.activeInspection = null; this.inspectionStats = { total_checked: 0, total_expected: 0, total_unknown: 0, progress_percent: 0 }; this.checkedEquipment = []; this.unknownBarcodes = []; this.lastScanResult = null; this.status = 'Проверка отменена'; } }, async loadInspectionHistory() { try { this.status = 'Загрузка истории проверок…'; this.error = ''; this.inspectionHistory = await this.fetchAuth('/inspections/sessions'); this.status = `Загружено ${this.inspectionHistory.length} проверок`; } catch (e) { console.error(e); this.error = 'Не удалось загрузить историю проверок: ' + e.message; this.status = ''; } }, async viewHistoryDetails(sessionId) { try { this.status = 'Загрузка деталей проверки…'; const [stats, details] = await Promise.all([ this.fetchAuth(`/inspections/sessions/${sessionId}`), this.fetchAuth(`/inspections/sessions/${sessionId}/records`) ]); // Показать активную проверку с данными истории this.activeInspection = stats.session; this.inspectionStats = stats; this.checkedEquipment = details.records; this.unknownBarcodes = details.unknown_barcodes; this.status = ''; } catch (e) { console.error(e); this.error = 'Не удалось загрузить детали: ' + e.message; this.status = ''; } } }, mounted() { // read auth from localStorage try { this.token = localStorage.getItem('access_token') || ''; this.role = localStorage.getItem('role') || ''; } catch {} this.loadAuditories(); this.loadOwners(); this.loadEquipmentTypes(); if (this.isAdmin) { this.loadUsers(); } } }).mount('#app');