40365-vm/assets/js/main.js
Flatlogic Bot 850d636d8a v1
2026-07-01 19:23:54 +00:00

203 lines
8.4 KiB
JavaScript

(() => {
const app = document.querySelector('[data-api-base]');
const apiBase = app.dataset.apiBase;
const state = {
user: null,
stores: [],
ovens: [],
parts: [],
maintenance: [],
};
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => Array.from(document.querySelectorAll(selector));
function toast(message, type = 'success') {
const region = $('#toastRegion');
const el = document.createElement('div');
el.className = `app-toast ${type === 'error' ? 'error' : ''}`;
el.textContent = message;
region.appendChild(el);
setTimeout(() => el.remove(), 3200);
}
function setScreen(screenId) {
$$('.screen').forEach((screen) => screen.classList.toggle('active', screen.id === screenId));
const active = document.getElementById(screenId);
$('#screenTitle').textContent = active?.dataset.title || 'ManutApp';
$('#logoutBtn').classList.toggle('d-none', screenId === 'loginScreen');
if (screenId === 'maintenanceScreen') renderMaintenance();
}
async function api(action, options = {}) {
const response = await fetch(`${apiBase}?action=${encodeURIComponent(action)}`, {
headers: { 'Content-Type': 'application/json' },
...options,
});
const payload = await response.json();
if (!response.ok || payload.success === false) throw new Error(payload.error || 'Falha na API mock.');
return payload;
}
async function bootstrap() {
try {
const payload = await api('bootstrap');
state.stores = payload.stores || [];
state.ovens = payload.ovens || [];
state.parts = payload.parts || [];
state.maintenance = payload.maintenance || [];
renderSeedLists();
renderFormOptions();
renderMaintenance();
} catch (error) {
toast(error.message, 'error');
}
}
function renderSeedLists() {
$('#storesList').innerHTML = state.stores.map((store) => `
<article class="list-item" tabindex="0"><div class="list-item-row"><strong>${escapeHtml(store.name)}</strong><span class="badge text-bg-light">${escapeHtml(store.id)}</span></div><small>${escapeHtml(store.city)}</small></article>
`).join('');
$('#partsList').innerHTML = state.parts.map((part) => `
<article class="list-item" tabindex="0"><div class="list-item-row"><strong>${escapeHtml(part.name)}</strong><span class="badge text-bg-light">${escapeHtml(part.sku)}</span></div><small>Peça mock para seleção em manutenção</small></article>
`).join('');
$('#ovensList').innerHTML = state.ovens.map((oven) => {
const store = state.stores.find((item) => item.id === oven.store_id);
return `<article class="list-item" tabindex="0"><div class="list-item-row"><strong>${escapeHtml(oven.name)}</strong><span class="badge text-bg-light">${escapeHtml(oven.model)}</span></div><small>${escapeHtml(store?.name || 'Loja')}</small></article>`;
}).join('');
}
function renderFormOptions() {
$('#storeSelect').innerHTML = '<option value="">Selecione uma loja</option>' + state.stores.map((store) => `<option value="${escapeAttr(store.id)}">${escapeHtml(store.name)}</option>`).join('');
renderOvenOptions();
$('#partsChoices').innerHTML = state.parts.map((part) => `
<label class="part-choice">
<input type="checkbox" value="${escapeAttr(part.id)}">
<span><strong>${escapeHtml(part.name)}</strong><br><small>${escapeHtml(part.sku)}</small></span>
</label>
`).join('');
}
function renderOvenOptions() {
const storeId = $('#storeSelect').value;
const ovens = state.ovens.filter((oven) => oven.store_id === storeId);
$('#ovenSelect').innerHTML = '<option value="">Selecione um forno</option>' + ovens.map((oven) => `<option value="${escapeAttr(oven.id)}">${escapeHtml(oven.name)} · ${escapeHtml(oven.model)}</option>`).join('');
}
function renderMaintenance() {
$('#maintenanceCount').textContent = state.maintenance.length === 1 ? '1 manutenção' : `${state.maintenance.length} manutenções`;
$('#maintenanceEmpty').classList.toggle('d-none', state.maintenance.length > 0);
$('#maintenanceList').innerHTML = state.maintenance.map((item) => `
<button class="list-item" type="button" data-detail-id="${escapeAttr(String(item.id))}">
<div class="list-item-row">
<strong>#${escapeHtml(String(item.id))} · ${escapeHtml(item.store_name)}</strong>
<span class="badge text-bg-light">${escapeHtml(item.status || 'Concluída')}</span>
</div>
<small>${escapeHtml(item.oven_name)} · ${escapeHtml(item.service_type)} · ${formatDate(item.created_at)}</small>
</button>
`).join('');
}
function renderDetail(id) {
const item = state.maintenance.find((entry) => String(entry.id) === String(id));
if (!item) {
toast('Registro não encontrado.', 'error');
return;
}
const parts = Array.isArray(item.parts) ? item.parts.map((part) => part.name).join(', ') : 'Não informado';
$('#detailCard').innerHTML = `
<p class="label">Registro #${escapeHtml(String(item.id))}</p>
<h2>${escapeHtml(item.store_name)}</h2>
<div class="detail-grid">
<div class="detail-row"><span>Forno</span><strong>${escapeHtml(item.oven_name)}</strong></div>
<div class="detail-row"><span>Técnico</span><strong>${escapeHtml(item.technician)}</strong></div>
<div class="detail-row"><span>Tipo</span><strong>${escapeHtml(item.service_type)}</strong></div>
<div class="detail-row"><span>Peças</span><strong>${escapeHtml(parts)}</strong></div>
<div class="detail-row"><span>Observações</span><p class="mb-0">${escapeHtml(item.notes || 'Sem observações.')}</p></div>
<div class="detail-row"><span>Criado em</span><strong>${formatDate(item.created_at)}</strong></div>
</div>
`;
setScreen('detailScreen');
}
function resetMaintenanceForm() {
$('#maintenanceForm').reset();
renderOvenOptions();
}
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>'"]/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#039;', '"': '&quot;' }[char]));
}
function escapeAttr(value) {
return escapeHtml(value).replace(/`/g, '&#096;');
}
function formatDate(value) {
if (!value) return 'agora';
const normalized = String(value).replace(' ', 'T');
const date = new Date(normalized);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' });
}
$('#loginForm').addEventListener('submit', (event) => {
event.preventDefault();
const username = $('#username').value.trim();
const password = $('#password').value.trim();
if (!username || !password) {
toast('Informe usuário e senha para continuar.', 'error');
return;
}
state.user = username;
$('#welcomeUser').textContent = `Olá, ${username}`;
toast('Login fake realizado com sucesso.');
setScreen('homeScreen');
});
$('#logoutBtn').addEventListener('click', () => {
state.user = null;
$('#loginForm').reset();
setScreen('loginScreen');
});
document.addEventListener('click', (event) => {
const nav = event.target.closest('[data-target]');
if (nav) setScreen(nav.dataset.target);
const detail = event.target.closest('[data-detail-id]');
if (detail) renderDetail(detail.dataset.detailId);
});
$('#storeSelect').addEventListener('change', renderOvenOptions);
$('#maintenanceForm').addEventListener('submit', async (event) => {
event.preventDefault();
const selectedParts = $$('#partsChoices input:checked').map((input) => input.value);
const payload = {
store_id: $('#storeSelect').value,
oven_id: $('#ovenSelect').value,
technician: $('#technicianInput').value.trim(),
service_type: $('#serviceTypeSelect').value,
parts: selectedParts,
notes: $('#notesInput').value.trim(),
};
if (!payload.store_id || !payload.oven_id || !payload.technician || selectedParts.length === 0) {
toast('Preencha loja, forno, técnico e pelo menos uma peça.', 'error');
return;
}
try {
const result = await api('maintenance', { method: 'POST', body: JSON.stringify(payload) });
state.maintenance = result.maintenance || [];
resetMaintenanceForm();
toast('Manutenção salva e listada com sucesso.');
setScreen('maintenanceScreen');
} catch (error) {
toast(error.message, 'error');
}
});
bootstrap();
})();