diff --git a/app.js b/app.js index 307e377..37ac6bc 100644 --- a/app.js +++ b/app.js @@ -2,6 +2,10 @@ const SUPABASE_URL = "https://qfjghplxbtogshfjkawx.supabase.co"; const SUPABASE_KEY = "sb_publishable_rIcKdaflOvJ0DLTJDcOrxA_bpTGG2hA"; const supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY); +const STORAGE_KEY = "dts-store-v3"; +const USERS_KEY = "users-store-v1"; +const SESSION_KEY = "users-session-v1"; + const STAR_PRODUCTS = [ ["20104425", "PANO SCOTT DURAMAX 58X03X08 BRANCO"], ["20092384", "GUARDANAPO SCALA 22X20 - 3600 UN"], @@ -26,33 +30,238 @@ const STAR_PRODUCTS = [ ["20111061", "TOAX430 2R 120F 3X6 SCALA WARM UP"] ]; +const STATUS_OPTIONS = ["EXPEDIDO", "CARREGANDO", "AG CHEGADA", "NO SHOW", "VEICULO RECUSADO", "SEPARANDO", "EM FATURAMENTO", "PATIO", "DT EXCLUDA", "FOI EMBORA"]; + +const STATUS_COLORS = { + EXPEDIDO: "#2e7d32", + CARREGANDO: "#1565c0", + "AG CHEGADA": "#f9a825", + "NO SHOW": "#6d4c41", + "VEICULO RECUSADO": "#c62828", + SEPARANDO: "#5e35b1", + "EM FATURAMENTO": "#00897b", + PATIO: "#ef6c00", + "DT EXCLUDA": "#546e7a", + "FOI EMBORA": "#37474f" +}; + +const STATUS_BASE = { + VENDA: { planejado: 59, realizado: 30 }, + TNF: { planejado: 0, realizado: 8 }, + MOGI: { planejado: 53, realizado: 37 }, + "Pré Fat Amazon": { planejado: 0, realizado: 0 }, + "TRANSFERÊNCIA": { planejado: 26, realizado: 26 }, + DESCARGA: { planejado: 0, realizado: 0 }, + DEVOLUÇÃO: { planejado: 0, realizado: 0 }, + GRADE: { planejado: 69, realizado: 69 } +}; + +let store = loadStore(); +let users = loadUsers(); +let currentUser = loadSession(); + const tabButtons = document.querySelectorAll(".tab-btn"); const panels = document.querySelectorAll(".tab-panel"); const statusEl = document.getElementById("status"); +const statusLocaisEl = document.getElementById("status-locais"); const resumoImportacaoEl = document.getElementById("resumo-importacao"); const listaDtsEl = document.getElementById("lista-dts"); - -const STORAGE_KEY = "dts-store-v2"; -let dtStore = loadStore(); +const gradeBody = document.getElementById("grade-body"); +const dashboardBody = document.getElementById("dashboard-body"); function loadStore() { const raw = localStorage.getItem(STORAGE_KEY); - return raw ? JSON.parse(raw) : {}; + const parsed = raw ? JSON.parse(raw) : {}; + return { dts: parsed.dts || {}, grade: parsed.grade || [], dashboard: parsed.dashboard || [] }; } function saveStore() { - localStorage.setItem(STORAGE_KEY, JSON.stringify(dtStore)); + localStorage.setItem(STORAGE_KEY, JSON.stringify(store)); +} + +function loadUsers() { + const raw = localStorage.getItem(USERS_KEY); + const parsed = raw ? JSON.parse(raw) : []; + const hasMaster = parsed.some((u) => u.role === "mestre"); + if (!hasMaster) { + parsed.push({ username: "mestre", password: "123456", role: "mestre" }); + localStorage.setItem(USERS_KEY, JSON.stringify(parsed)); + } + return parsed; +} + +function saveUsers() { + localStorage.setItem(USERS_KEY, JSON.stringify(users)); +} + +function loadSession() { + const raw = localStorage.getItem(SESSION_KEY); + return raw ? JSON.parse(raw) : null; +} + +function saveSession(user) { + localStorage.setItem(SESSION_KEY, JSON.stringify(user)); +} + +function clearSession() { + localStorage.removeItem(SESSION_KEY); +} + +function setupAuth() { + const authScreen = document.getElementById("auth-screen"); + const appShell = document.getElementById("app-shell"); + const loginForm = document.getElementById("login-form"); + const registerForm = document.getElementById("register-form"); + const authStatus = document.getElementById("auth-status"); + + document.getElementById("show-login").addEventListener("click", () => { + loginForm.classList.remove("hidden"); + registerForm.classList.add("hidden"); + }); + + document.getElementById("show-register").addEventListener("click", () => { + registerForm.classList.remove("hidden"); + loginForm.classList.add("hidden"); + }); + + loginForm.addEventListener("submit", (event) => { + event.preventDefault(); + const username = document.getElementById("login-user").value.trim(); + const password = document.getElementById("login-pass").value; + const found = users.find((u) => u.username === username && u.password === password); + if (!found) { + authStatus.textContent = "Usuário ou senha inválidos."; + return; + } + currentUser = { username: found.username, role: found.role }; + saveSession(currentUser); + authStatus.textContent = ""; + authScreen.classList.add("hidden"); + appShell.classList.remove("hidden"); + onAuthenticated(); + }); + + registerForm.addEventListener("submit", (event) => { + event.preventDefault(); + const username = document.getElementById("register-user").value.trim(); + const password = document.getElementById("register-pass").value; + if (!username || !password) { + authStatus.textContent = "Preencha usuário e senha."; + return; + } + if (users.some((u) => u.username === username)) { + authStatus.textContent = "Usuário já existe."; + return; + } + users.push({ username, password, role: "padrao" }); + saveUsers(); + authStatus.textContent = "Registro concluído. Agora faça login."; + registerForm.reset(); + loginForm.classList.remove("hidden"); + registerForm.classList.add("hidden"); + }); + + document.getElementById("logout-btn").addEventListener("click", () => { + clearSession(); + currentUser = null; + appShell.classList.add("hidden"); + authScreen.classList.remove("hidden"); + }); + + if (currentUser) { + authScreen.classList.add("hidden"); + appShell.classList.remove("hidden"); + onAuthenticated(); + } +} + +function onAuthenticated() { + document.getElementById("current-user").textContent = `Usuário: ${currentUser.username} (${currentUser.role})`; + applyRoleVisibility(); + setupAdminPanel(); + bindAppEvents(); + renderResumoImportacao(); + pesquisarPorFinalDT(); + renderGrade(); + renderDashboard(); + renderStatusPage(); + renderStarProducts(); } -tabButtons.forEach((btn) => { - btn.addEventListener("click", () => { - const target = btn.dataset.tab; - tabButtons.forEach((b) => b.classList.remove("active")); - panels.forEach((p) => p.classList.remove("active")); - btn.classList.add("active"); - document.getElementById(target).classList.add("active"); +function applyRoleVisibility() { + const role = currentUser?.role || "padrao"; + document.querySelectorAll(".role-protected").forEach((el) => { + const allowed = String(el.dataset.roles || "").split(",").map((v) => v.trim()); + el.classList.toggle("hidden", !allowed.includes(role)); }); -}); + + if (document.querySelector(".tab-btn.active.hidden")) { + const first = document.querySelector(".tab-btn:not(.hidden)"); + if (first) first.click(); + } +} + +function setupAdminPanel() { + const panel = document.getElementById("admin-panel"); + const select = document.getElementById("promote-user"); + const adminStatus = document.getElementById("admin-status"); + if (currentUser?.role !== "mestre") { + panel.classList.add("hidden"); + return; + } + + panel.classList.remove("hidden"); + select.innerHTML = ""; + users.filter((u) => u.role !== "mestre" && u.role !== "analista").forEach((u) => { + const opt = document.createElement("option"); + opt.value = u.username; + opt.textContent = u.username; + select.appendChild(opt); + }); + + document.getElementById("promote-btn").onclick = () => { + const username = select.value; + const found = users.find((u) => u.username === username); + if (!found) { + adminStatus.textContent = "Nenhum usuário padrão disponível para promoção."; + return; + } + found.role = "analista"; + saveUsers(); + adminStatus.textContent = `${username} promovido para analista.`; + setupAdminPanel(); + }; +} + +let appEventsBound = false; +function bindAppEvents() { + if (appEventsBound) return; + appEventsBound = true; + + tabButtons.forEach((btn) => { + btn.addEventListener("click", () => { + if (btn.classList.contains("hidden")) return; + const target = btn.dataset.tab; + tabButtons.forEach((b) => b.classList.remove("active")); + panels.forEach((p) => p.classList.remove("active")); + btn.classList.add("active"); + document.getElementById(target).classList.add("active"); + }); + }); + + document.getElementById("importar-sap").addEventListener("click", () => importarMateriais().catch((e) => (statusEl.textContent = `Erro: ${e.message}`))); + document.getElementById("importar-locais").addEventListener("click", () => importarLocais().catch((e) => (statusLocaisEl.textContent = `Erro: ${e.message}`))); + document.getElementById("pesquisar-dt").addEventListener("click", pesquisarPorFinalDT); + document.getElementById("carregar-dt").addEventListener("click", () => { + const dt = listaDtsEl.value; + if (!dt || !store.dts[dt]) { + alert("Selecione uma DT válida."); + return; + } + fillSheet(store.dts[dt]); + }); + document.getElementById("imprimir").addEventListener("click", () => window.print()); +} function formatDateFromSAP(value) { const clean = String(value || "").trim(); @@ -63,21 +272,33 @@ function formatDateFromSAP(value) { return clean; } -async function buscarPadraoPorSku(sku) { - const { data, error } = await supabaseClient - .from("produtos") - .select("sku, fardos_por_palete") - .eq("sku", sku) - .limit(1); +function parseSheetFromFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (event) => { + try { + const data = new Uint8Array(event.target.result); + const wb = XLSX.read(data, { type: "array" }); + const ws = wb.Sheets[wb.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(ws, { header: 1, raw: false }); + resolve(rows); + } catch (error) { + reject(error); + } + }; + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }); +} +async function buscarPadraoPorSku(sku) { + const { data, error } = await supabaseClient.from("produtos").select("sku, fardos_por_palete").eq("sku", sku).limit(1); if (error || !data?.length) return null; return Number(data[0].fardos_por_palete || 0); } -async function parseSapLineToItem(line) { - const cols = line.split("\t").map((v) => v.trim()); +async function parseSapColsToItem(cols) { if (cols.length < 14) return null; - const material = cols[6]; const qtd = Number(cols[5] || 0); const padraoSupabase = await buscarPadraoPorSku(material); @@ -106,22 +327,14 @@ async function parseSapLineToItem(line) { }; } -async function importarLinhasSAP() { - const raw = document.getElementById("sap-linhas").value.trim(); - if (!raw) { - statusEl.textContent = "Cole uma ou mais linhas do SAP."; - return; - } - - const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean); +async function importSAPRows(rows) { let importadas = 0; - - for (const line of lines) { - const parsed = await parseSapLineToItem(line); + for (const row of rows) { + const parsed = await parseSapColsToItem(row.map((v) => String(v || "").trim())); if (!parsed?.numeroTransporte) continue; - if (!dtStore[parsed.numeroTransporte]) { - dtStore[parsed.numeroTransporte] = { + if (!store.dts[parsed.numeroTransporte]) { + store.dts[parsed.numeroTransporte] = { codCliente: parsed.codCliente, codTransportadora: parsed.codTransportadora, numeroTransporte: parsed.numeroTransporte, @@ -138,21 +351,257 @@ async function importarLinhasSAP() { }; } - dtStore[parsed.numeroTransporte].itens.push(parsed.item); + store.dts[parsed.numeroTransporte].itens.push(parsed.item); importadas += 1; } + return importadas; +} + +async function importarMateriais() { + const text = document.getElementById("sap-linhas").value.trim(); + const file = document.getElementById("sap-file").files[0]; + let rows = []; + + if (text) { + rows = text.split("\n").map((line) => line.split("\t").map((v) => v.trim())).filter((c) => c.length >= 14); + } else if (file) { + const fileRows = await parseSheetFromFile(file); + rows = fileRows.filter((r) => r.filter(Boolean).length).map((r) => r.map((v) => String(v || "").trim())); + } else { + statusEl.textContent = "Cole linhas SAP ou envie uma planilha."; + return; + } + const total = await importSAPRows(rows); saveStore(); renderResumoImportacao(); - statusEl.textContent = `${importadas} linha(s) importada(s) e agrupada(s) por DT.`; + pesquisarPorFinalDT(); + statusEl.textContent = `${total} linha(s) de materiais importada(s).`; } -document.getElementById("importar-sap").addEventListener("click", importarLinhasSAP); +function parseLocaisByPosition(row) { + return { + dt: String(row[2] || "").trim(), + local: String(row[4] || "").trim(), + cidadeDestino: String(row[7] || "").trim(), + peso: Number(String(row[9] || "0").replace(",", ".")) || 0, + transportadora: String(row[12] || "").trim(), + motorista: String(row[14] || "").trim(), + placaVeiculo: String(row[16] || "").trim(), + placaComp1: String(row[17] || "").trim(), + placaComp2: String(row[18] || "").trim(), + agenda: String(row[20] || "").trim(), + fimAgenda: String(row[22] || "").trim() + }; +} + +function ensureGradeRow(item) { + const idx = store.grade.findIndex((g) => g.dt === item.dt); + const base = { + dt: item.dt, + transportadora: item.transportadora, + gradeCarregamento: item.agenda, + fimCarregamento: item.fimAgenda, + horaChegada: "", + status: "AG CHEGADA", + tipo: item.local, + toneladas: item.peso, + agenda: item.agenda, + placa: item.placaVeiculo || item.placaComp1 || item.placaComp2, + cidadeDestino: item.cidadeDestino, + peso: item.peso + }; + + if (idx === -1) store.grade.push(base); + else store.grade[idx] = { ...store.grade[idx], ...base, status: store.grade[idx].status || "AG CHEGADA", horaChegada: store.grade[idx].horaChegada || "" }; +} + +async function importarLocais() { + const file = document.getElementById("locais-file").files[0]; + if (!file) { + statusLocaisEl.textContent = "Selecione a planilha de shipment."; + return; + } + const rows = await parseSheetFromFile(file); + let count = 0; + rows.slice(1).forEach((row) => { + const item = parseLocaisByPosition(row); + if (!item.dt) return; + ensureGradeRow(item); + count += 1; + }); + saveStore(); + renderGrade(); + renderStatusPage(); + statusLocaisEl.textContent = `${count} registro(s) importado(s) para a grade.`; +} + +function upsertDashboardFromPatio(gradeItem) { + const idx = store.dashboard.findIndex((d) => d.dt === gradeItem.dt); + const now = new Date(); + const hora = now.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" }); + const data = now.toLocaleDateString("pt-BR"); + + if (idx === -1) { + store.dashboard.push({ + dt: gradeItem.dt, + hora, + data, + placa: gradeItem.placa || "", + transportadora: gradeItem.transportadora || "", + status: gradeItem.status, + mapa: gradeItem.cidadeDestino || "", + grade: gradeItem.gradeCarregamento || "", + tipo: gradeItem.tipo || "", + peso: gradeItem.peso || 0 + }); + return; + } + + store.dashboard[idx] = { + ...store.dashboard[idx], + dt: gradeItem.dt, + placa: gradeItem.placa || store.dashboard[idx].placa, + transportadora: gradeItem.transportadora || store.dashboard[idx].transportadora, + status: gradeItem.status, + mapa: gradeItem.cidadeDestino || store.dashboard[idx].mapa, + grade: gradeItem.gradeCarregamento || store.dashboard[idx].grade, + tipo: gradeItem.tipo || store.dashboard[idx].tipo, + peso: gradeItem.peso || store.dashboard[idx].peso + }; +} + +function renderGrade() { + gradeBody.innerHTML = ""; + if (!store.grade.length) { + gradeBody.innerHTML = 'Nenhuma linha importada.'; + return; + } + + store.grade.forEach((row, index) => { + const tr = document.createElement("tr"); + tr.innerHTML = ` + ${row.dt || "-"} + ${row.transportadora || "-"} + ${row.gradeCarregamento || "-"} + ${row.fimCarregamento || "-"} + + + + + ${row.tipo || "-"} + ${Number(row.toneladas || 0).toFixed(2)} + ${row.agenda || "-"} + `; + gradeBody.appendChild(tr); + }); + + document.querySelectorAll("[data-grade-time]").forEach((el) => { + el.addEventListener("change", (e) => { + const idx = Number(e.target.dataset.gradeTime); + store.grade[idx].horaChegada = e.target.value; + saveStore(); + }); + }); + + document.querySelectorAll("[data-grade-status]").forEach((el) => { + el.addEventListener("change", (e) => { + const idx = Number(e.target.dataset.gradeStatus); + store.grade[idx].status = e.target.value; + e.target.style.background = STATUS_COLORS[e.target.value] || "#eceff1"; + if (e.target.value === "PATIO") upsertDashboardFromPatio(store.grade[idx]); + saveStore(); + renderDashboard(); + renderStatusPage(); + }); + }); +} + +function renderDashboard() { + dashboardBody.innerHTML = ""; + if (!store.dashboard.length) { + dashboardBody.innerHTML = 'Nenhum veículo em pátio.'; + return; + } + + store.dashboard.forEach((row, index) => { + const tr = document.createElement("tr"); + tr.innerHTML = ` + ${row.dt || "-"} + + + ${row.placa || "-"} + ${row.transportadora || "-"} + ${row.status} + ${row.mapa || "-"} + ${row.grade || "-"} + ${row.tipo || "-"} + ${Number(row.peso || 0).toFixed(2)} + `; + dashboardBody.appendChild(tr); + }); + + document.querySelectorAll("[data-dash-hora]").forEach((el) => { + el.addEventListener("change", (e) => { + store.dashboard[Number(e.target.dataset.dashHora)].hora = e.target.value; + saveStore(); + }); + }); + + document.querySelectorAll("[data-dash-data]").forEach((el) => { + el.addEventListener("change", (e) => { + store.dashboard[Number(e.target.dataset.dashData)].data = fromIsoDate(e.target.value); + saveStore(); + }); + }); +} + +function toIsoDate(brDate) { + const parts = String(brDate || "").split("/"); + if (parts.length !== 3) return ""; + return `${parts[2]}-${parts[1].padStart(2, "0")}-${parts[0].padStart(2, "0")}`; +} + +function fromIsoDate(iso) { + if (!iso) return ""; + const [yyyy, mm, dd] = iso.split("-"); + return `${dd}/${mm}/${yyyy}`; +} + +function renderStatusPage() { + const body = document.getElementById("status-body"); + const charts = document.getElementById("charts"); + body.innerHTML = ""; + charts.innerHTML = ""; + + const gradeRealizado = store.grade.filter((g) => ["EXPEDIDO", "PATIO", "CARREGANDO"].includes(g.status)).length; + const merged = JSON.parse(JSON.stringify(STATUS_BASE)); + merged.GRADE.realizado = gradeRealizado; + + Object.entries(merged).forEach(([label, values]) => { + const pend = Number(values.planejado) - Number(values.realizado); + const tr = document.createElement("tr"); + tr.innerHTML = `${label}${values.planejado}${values.realizado}${pend}`; + body.appendChild(tr); + + const max = Math.max(values.planejado, values.realizado, Math.abs(pend), 1); + const card = document.createElement("div"); + card.className = "chart-card"; + card.innerHTML = ` +

${label}

+
Planejado
${values.planejado}
+
Realizado
${values.realizado}
+
Pend
${pend}
+ `; + charts.appendChild(card); + }); +} function renderResumoImportacao() { resumoImportacaoEl.innerHTML = ""; - const registros = Object.values(dtStore); - + const registros = Object.values(store.dts); if (!registros.length) { resumoImportacaoEl.innerHTML = 'Nenhuma DT importada ainda.'; return; @@ -161,22 +610,17 @@ function renderResumoImportacao() { registros.forEach((dt) => { const total = (dt.itens || []).reduce((acc, item) => acc + Number(item.qtd || 0), 0); const tr = document.createElement("tr"); - tr.innerHTML = ` - ${dt.numeroTransporte} - ${dt.nomeCliente || "-"} - ${dt.itens?.length || 0} - ${total} - `; + tr.innerHTML = `${dt.numeroTransporte}${dt.nomeCliente || "-"}${dt.itens?.length || 0}${total}`; resumoImportacaoEl.appendChild(tr); }); } function pesquisarPorFinalDT() { const term = document.getElementById("busca-dt").value.trim(); - const allDts = Object.keys(dtStore); + const allDts = Object.keys(store.dts); const matches = allDts.filter((dt) => !term || dt.endsWith(term)); - listaDtsEl.innerHTML = ""; + if (!matches.length) { listaDtsEl.innerHTML = ''; return; @@ -185,13 +629,11 @@ function pesquisarPorFinalDT() { matches.forEach((dt) => { const option = document.createElement("option"); option.value = dt; - option.textContent = `${dt} - ${dtStore[dt].nomeCliente || "Sem cliente"}`; + option.textContent = `${dt} - ${store.dts[dt].nomeCliente || "Sem cliente"}`; listaDtsEl.appendChild(option); }); } -document.getElementById("pesquisar-dt").addEventListener("click", pesquisarPorFinalDT); - function fillSheet(data) { document.getElementById("sheet-dt").textContent = `DT: ${data.numeroTransporte || "-"}`; document.getElementById("sheet-cliente").textContent = `Cliente: ${data.nomeCliente || "-"}`; @@ -220,19 +662,9 @@ function fillSheet(data) { document.getElementById("sheet-total").textContent = total; } -document.getElementById("carregar-dt").addEventListener("click", () => { - const dt = listaDtsEl.value; - if (!dt || !dtStore[dt]) { - alert("Selecione uma DT válida."); - return; - } - fillSheet(dtStore[dt]); -}); - -document.getElementById("imprimir").addEventListener("click", () => window.print()); - function renderStarProducts() { const body = document.getElementById("produtos-estrela-body"); + if (body.childNodes.length) return; STAR_PRODUCTS.forEach(([sku, descricao]) => { const tr = document.createElement("tr"); tr.innerHTML = `${sku}${descricao}`; @@ -240,7 +672,4 @@ function renderStarProducts() { }); } -renderResumoImportacao(); -pesquisarPorFinalDT(); -renderStarProducts(); - \ No newline at end of file +setupAuth(); diff --git a/index.html b/index.html index 9a8fce6..4b6a934 100644 --- a/index.html +++ b/index.html @@ -7,123 +7,261 @@ -
-

Painel Logístico

-

Importação em lote do SAP, relatório por DT e produtos estrela.

-
- - - -
-
-

Importação SAP (múltiplas linhas)

-
- -

Formato esperado por linha (TAB): Cód Cliente, Cód Transportadora, Nº transporte, Inf Agenda, Nome Cliente, Qtde Remessa, Material, Setor, Cliente Pallet, Agrupamento Regional, Qtd Teórica Convertida, Nº NFe, Quantidade Teórica, Data Agendamento.

- -

-
+
+
+

Painel Logístico

+

Acesse com seu usuário ou faça um novo registro.

-
-

DTs importadas no navegador

- - - - - - - - - - -
DTClienteItensTotal fardos
+
+ +
-
-
-

Relatório para Impressão

-
-
+
+ + -
-

Anotações do Conferente

-
-
- -

Lacre: _____________________________________________

- -
- -
-

Produtos Estrela

- - - - - - - - -
SKUDescrição
-
-
- + - diff --git a/styles.css b/styles.css index ab8259f..4e8ba1c 100644 --- a/styles.css +++ b/styles.css @@ -15,12 +15,49 @@ main { padding: 1rem; } +.hidden { + display: none !important; +} + +.auth-screen { + min-height: 100vh; + display: grid; + place-items: center; + padding: 1rem; +} + +.auth-card { + width: min(440px, 95vw); + background: #fff; + border: 1px solid #d8dce1; + border-radius: 14px; + padding: 1rem; +} + +.auth-toggle { + display: flex; + gap: 0.5rem; + margin-bottom: 0.8rem; +} + +.auth-form { + display: grid; + gap: 0.6rem; +} + +.user-bar { + display: flex; + gap: 0.75rem; + align-items: center; +} + .tabs { display: flex; gap: 0.5rem; padding: 0 1rem 1rem; max-width: 1200px; margin: 0 auto; + flex-wrap: wrap; } .tab-btn { @@ -45,13 +82,6 @@ main { display: block; } -.grid-form { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 0.75rem; - margin-bottom: 1rem; -} - label { display: flex; flex-direction: column; @@ -60,6 +90,7 @@ label { } input, +select, button, textarea { font: inherit; @@ -86,12 +117,6 @@ button.primary { border: 1px solid #ddd; } -.row-title { - display: flex; - justify-content: space-between; - align-items: center; -} - table { width: 100%; border-collapse: collapse; @@ -105,6 +130,71 @@ td { padding: 0.45rem; text-align: left; font-size: 0.9rem; + vertical-align: middle; +} + +.status-select { + color: #fff; + border: none; +} + +.status-chip { + color: #fff; + border-radius: 999px; + padding: 0.2rem 0.6rem; + display: inline-block; +} + +.inline-input { + width: 100%; + min-width: 110px; + box-sizing: border-box; +} + +.charts-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 0.8rem; +} + +.chart-card { + background: #fff; + border: 1px solid #dde3ea; + border-radius: 10px; + padding: 0.75rem; +} + +.chart-card h4 { + margin: 0 0 0.5rem; +} + +.bar-row { + display: grid; + grid-template-columns: 80px 1fr 40px; + align-items: center; + gap: 0.45rem; + margin-bottom: 0.3rem; +} + +.bar { + background: #e9eef4; + border-radius: 999px; + overflow: hidden; + height: 12px; +} + +.bar i { + display: block; + height: 100%; + background: #375a7f; +} + +.bar.done i { + background: #2e7d32; +} + +.bar.pend i { + background: #c62828; } .print-sheet { @@ -145,11 +235,16 @@ td { background: white; } + #auth-screen, header, .tabs, #relatorio .sub-card, #zles002, - #produtos-estrela { + #grade, + #dashboard, + #status-page, + #produtos-estrela, + #mb51 { display: none !important; } @@ -168,12 +263,3 @@ td { color: #555; font-size: 0.85rem; } - -.resumo-cabecalho { - margin-top: 0.5rem; - padding: 0.5rem; - background: #eef5ff; - border: 1px solid #c7dbff; - border-radius: 8px; -} - \ No newline at end of file