203 lines
8.4 KiB
JavaScript
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) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[char]));
|
|
}
|
|
|
|
function escapeAttr(value) {
|
|
return escapeHtml(value).replace(/`/g, '`');
|
|
}
|
|
|
|
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();
|
|
})();
|