diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index 392d9e0..440fdeb 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -31,4 +31,5 @@ //= link app/order_service_received_items.js //= link app/knowledge_base_modal.js //= link cliente/budget_approvals.js -//= link controllers/onboarding_welcome_controller.js \ No newline at end of file +//= link controllers/onboarding_welcome_controller.js +//= link controllers/onboarding_checklist_controller.js diff --git a/app/controllers/app/dashboard_controller.rb b/app/controllers/app/dashboard_controller.rb index 824f4c0..1d6e136 100644 --- a/app/controllers/app/dashboard_controller.rb +++ b/app/controllers/app/dashboard_controller.rb @@ -156,6 +156,8 @@ def set_onboarding_welcome_modal @onboarding_progress = current_user.user_onboarding_progress @show_onboarding_welcome_modal = onboarding_welcome_eligible? @onboarding_start_path = next_onboarding_step_path + @show_onboarding_checklist = !current_user.tecnico? + @onboarding_checklist_steps = onboarding_checklist_steps end def onboarding_welcome_eligible? @@ -177,4 +179,14 @@ def next_onboarding_step_path route_name = ONBOARDING_STEP_PATHS[next_step] || :app_dashboard_path send(route_name) end + + def onboarding_checklist_steps + [ + { key: "created_technician", label: "Cadastrar técnico", path: app_technicians_path }, + { key: "created_customer", label: "Cadastrar cliente", path: app_clients_path }, + { key: "created_first_work_order", label: "Criar primeira ordem de serviço", path: app_order_services_path }, + { key: "moved_work_order_status", label: "Atualizar status da ordem de serviço", path: app_order_services_path }, + { key: "viewed_reports", label: "Visualizar relatórios", path: app_reports_path } + ] + end end diff --git a/app/javascript/controllers/onboarding_checklist_controller.js b/app/javascript/controllers/onboarding_checklist_controller.js new file mode 100644 index 0000000..052430b --- /dev/null +++ b/app/javascript/controllers/onboarding_checklist_controller.js @@ -0,0 +1,166 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = [ + "progressBar", + "progressLabel", + "titleLabel", + "stepsList", + "summary", + "details", + "toggleButton", + "errorBox" + ] + + static values = { + endpoint: String, + steps: Array, + refreshMs: { type: Number, default: 20000 } + } + + connect() { + this.detailsExpanded = false + this.refresh() + this.startAutoRefresh() + this.boundVisibilityHandler = this.handleVisibilityChange.bind(this) + document.addEventListener("visibilitychange", this.boundVisibilityHandler) + } + + disconnect() { + this.stopAutoRefresh() + if (this.boundVisibilityHandler) { + document.removeEventListener("visibilitychange", this.boundVisibilityHandler) + } + } + + async refresh() { + if (!this.hasEndpointValue || !this.endpointValue) return + + try { + const response = await fetch(this.endpointValue, { + method: "GET", + headers: { "Accept": "application/json" }, + credentials: "same-origin" + }) + + if (!response.ok) throw new Error(`HTTP ${response.status}`) + + const payload = await response.json() + this.renderPayload(payload.onboarding_progress || {}) + this.hideError() + } catch (error) { + this.showError("Não foi possível carregar o checklist de onboarding agora.") + console.warn("[OnboardingChecklist] refresh error", error) + } + } + + toggleDetails() { + this.detailsExpanded = !this.detailsExpanded + this.applyDetailsVisibility() + } + + handleVisibilityChange() { + if (!document.hidden) { + this.refresh() + } + } + + startAutoRefresh() { + this.stopAutoRefresh() + this.refreshInterval = window.setInterval(() => this.refresh(), this.refreshMsValue) + } + + stopAutoRefresh() { + if (!this.refreshInterval) return + window.clearInterval(this.refreshInterval) + this.refreshInterval = null + } + + renderPayload(data) { + const completedSteps = data.completed_steps || {} + const completedCount = Number(data.completed_steps_count || 0) + const stepsTotal = Number(data.steps_total || this.stepsValue.length || 0) + const progress = Number(data.progress_percentage || 0) + const finished = data.finished === true + + if (this.hasProgressBarTarget) { + this.progressBarTarget.style.width = `${progress}%` + this.progressBarTarget.setAttribute("aria-valuenow", String(progress)) + } + + if (this.hasProgressLabelTarget) { + this.progressLabelTarget.textContent = `${completedCount}/${stepsTotal}` + } + + if (this.hasTitleLabelTarget) { + this.titleLabelTarget.textContent = finished ? "Onboarding concluído" : "Primeiros passos" + } + + this.renderSteps(completedSteps) + + if (finished) { + this.detailsExpanded = false + this.summaryTarget.classList.remove("d-none") + } else { + this.detailsExpanded = true + this.summaryTarget.classList.add("d-none") + } + + this.applyDetailsVisibility() + } + + renderSteps(completedSteps) { + if (!this.hasStepsListTarget) return + + const html = this.stepsValue.map((step) => { + const done = completedSteps[step.key] === true + const statusBadge = done + ? 'Concluído' + : 'Pendente' + + const action = done + ? "" + : `Ir agora` + + return ` +