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 ` +
  • +
    + + ${step.label} +
    +
    + ${statusBadge} + ${action} +
    +
  • + ` + }).join("") + + this.stepsListTarget.innerHTML = html + } + + applyDetailsVisibility() { + if (!this.hasDetailsTarget) return + + if (this.detailsExpanded) { + this.detailsTarget.classList.remove("d-none") + if (this.hasToggleButtonTarget) this.toggleButtonTarget.textContent = "Ocultar" + } else { + this.detailsTarget.classList.add("d-none") + if (this.hasToggleButtonTarget) this.toggleButtonTarget.textContent = "Ver checklist" + } + } + + showError(message) { + if (!this.hasErrorBoxTarget) return + + this.errorBoxTarget.textContent = message + this.errorBoxTarget.classList.remove("d-none") + } + + hideError() { + if (!this.hasErrorBoxTarget) return + + this.errorBoxTarget.classList.add("d-none") + } +} diff --git a/app/views/app/dashboard/_onboarding_checklist.html.erb b/app/views/app/dashboard/_onboarding_checklist.html.erb new file mode 100644 index 0000000..483d190 --- /dev/null +++ b/app/views/app/dashboard/_onboarding_checklist.html.erb @@ -0,0 +1,47 @@ +
    +
    +
    +
    +
    Primeiros passos
    +
    + 0/5 + +
    +
    + +
    +
    +
    +
    + +
      +
      + +
      + +
      + + +
      +
      +
      diff --git a/app/views/app/dashboard/index.html.erb b/app/views/app/dashboard/index.html.erb index dea8b0f..79a0f79 100644 --- a/app/views/app/dashboard/index.html.erb +++ b/app/views/app/dashboard/index.html.erb @@ -1,6 +1,7 @@
      <%= render "onboarding_welcome_modal" %> + <%= render "onboarding_checklist" if @show_onboarding_checklist %> <% flash.each do |type, message| %> <% bootstrap_class = { notice: "alert-success", alert: "alert-danger", error: "alert-danger" }[type.to_sym] || "alert-info" %>