Skip to content

Commit

Permalink
Prepare for multiple surveys
Browse files Browse the repository at this point in the history
  • Loading branch information
IciaCarroBarallobre committed Jan 14, 2025
1 parent 829c288 commit 21b217f
Show file tree
Hide file tree
Showing 9 changed files with 1,610 additions and 153 deletions.
16 changes: 10 additions & 6 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@ import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"

let Hooks = {};
Hooks.ClipboardCopy = {

Hooks.ScrollToTop = {
mounted() {
this.handleEvent("clipboard_copy", ({ text }) => {
navigator.clipboard.writeText(text).then(() => {
alert("Link copied to clipboard!");
}).catch(() => {
alert("Failed to copy the link!");
const scrollToTopBtn = this.el;

scrollToTopBtn.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
};

export default Hooks;

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: Hooks});
liveSocket.connect();
Expand Down
38 changes: 20 additions & 18 deletions lib/exploring_beam_community_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ defmodule ExploringBeamCommunityWeb.CoreComponents do
role="alert"
class={[
"fixed top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
@kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
@kind == :info && "bg-main-100 text-main-900 ring-main-500 fill-main-900",
@kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
]}
{@rest}
Expand Down Expand Up @@ -671,21 +671,23 @@ defmodule ExploringBeamCommunityWeb.CoreComponents do
|> JS.pop_focus()
end

@doc """
Translates an error message. This can be modified to return a custom error message.
"""
def translate_error({msg, opts}) do
# You can replace this with a custom translation logic,
# or directly return the message if no translation is needed
"#{msg} #{opts}"
end

@doc """
Translates errors for a specific field from a list of errors.
"""
def translate_errors(errors, field) when is_list(errors) do
errors
|> Enum.filter(fn {key, _} -> key == field end) # Filter out errors for the specific field
|> Enum.map(fn {_, {msg, opts}} -> translate_error({msg, opts}) end) # Translate each error
end
@doc """
Translates an error message. This can be modified to return a custom error message.
"""
def translate_error({msg, opts}) do
# You can replace this with a custom translation logic,
# or directly return the message if no translation is needed
"#{msg} #{opts}"
end

@doc """
Translates errors for a specific field from a list of errors.
"""
def translate_errors(errors, field) when is_list(errors) do
errors
# Filter out errors for the specific field
|> Enum.filter(fn {key, _} -> key == field end)
# Translate each error
|> Enum.map(fn {_, {msg, opts}} -> translate_error({msg, opts}) end)
end
end
25 changes: 4 additions & 21 deletions lib/exploring_beam_community_web/components/question.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,14 @@ defmodule ExploringBeamCommunityWeb.Components.Question do
{:noreply, update(socket, :shown_all, fn shown -> not shown end)}
end

@impl true
def handle_event("copy_link", %{"question_id" => question_id}, socket) do
base_url = socket.assigns.base_url
shareable_link = "#{base_url}/results##{question_id}"

{:noreply, push_event(socket, "clipboard_copy", %{text: shareable_link})}
end

@impl true
def render(assigns) do
~H"""
<div class="py-12 border-b border-gray-500" id={"q-#{@id}"}>
<div class="flex">
<button
type="button"
phx-target={@myself}
phx-click={JS.push("copy_link", value: %{question_id: "q-#{@id}"})}
class="text-gray-400 hover:text-blue-700 flex items-center gap-1"
title="Copy link to this question"
>
<.icon name="hero-link" class=" mb-3 w-5 h-5 text-gray-400 hover:text-blue-700 "/>
</button>
<p class="ml-2 text-xl mb-3 font-light">
<strong><%= @question["QuestionText"] %></strong>
</p>
<p class="ml-2 text-xl mb-3 font-light">
<strong><%= @question["QuestionText"] %></strong>
</p>
</div>
Expand All @@ -68,7 +51,7 @@ defmodule ExploringBeamCommunityWeb.Components.Question do
</button>
<%end%>
<.multiple_choice_question options={@question["Options"]} shown_all={@shown_all} top={@top} />
<.multiple_choice_question options={@question["Options"]} total_answers={@question["TotalAnswers"]} shown_all={@shown_all} top={@top} />
<% "Range" -> %>
<.range_question options={@question["Options"]} />
<% "Single-choice" -> %>
Expand Down
21 changes: 11 additions & 10 deletions lib/exploring_beam_community_web/components/type_of_question.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule ExploringBeamCommunityWeb.Components.TypeOfQuestion do
use Phoenix.Component

attr(:options, :list, required: true)
attr(:total_answers, :integer, required: true)
attr(:top, :integer, default: 5)
attr(:shown_all, :boolean, default: true)

Expand All @@ -16,11 +17,12 @@ defmodule ExploringBeamCommunityWeb.Components.TypeOfQuestion do
<span class="text-sm font-bold mb-1"><%= option["Label"] %></span>
<span class="text-right font-semibold my-2">
<span class="text-xs mx-3"><%= option["Responses"] %> resp.</span>
<span class="text-sm"><%= parse_percentage(option["Percentage"]) || "N/A" %></span>
<span class="text-sm"><%= calculate_percentage_formated(option["Responses"], @total_answers) || "N/A" %></span>
</span>
</span>
<div class="h-6 rounded-md bg-main-100 relative">
<div class="bg-main-500 h-full rounded-md" style={"width: #{String.replace(parse_percentage(option["Percentage"] || "0%"), "%", "")}%;"}></div>
<div class="bg-main-500 h-full rounded-md"
style={"width: #{calculate_percentage(option["Responses"], @total_answers)}%;"}></div>
</div>
</div>
<% end %>
Expand Down Expand Up @@ -55,15 +57,14 @@ defmodule ExploringBeamCommunityWeb.Components.TypeOfQuestion do
"""
end

defp parse_percentage(nil), do: "N/A"
defp calculate_percentage(responses, total_answers) do
responses / total_answers * 100
end

defp parse_percentage(percentage) when is_binary(percentage) do
percentage
|> String.replace("%", "")
|> String.to_float()
|> Float.round(1)
|> Kernel.to_string()
|> Kernel.<>("%")
defp calculate_percentage_formated(responses, total_answers) do
percentage = calculate_percentage(responses, total_answers)
formatted = :io_lib.format("~.1f", [percentage])
"#{formatted}%"
end

def list_to_text(list) do
Expand Down
175 changes: 125 additions & 50 deletions lib/exploring_beam_community_web/live/results.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,84 +4,159 @@ defmodule ExploringBeamCommunityWeb.ResultsLive do

def mount(_params, _session, socket) do
priv_dir = Application.app_dir(:exploring_beam_community, "priv/static")
file_path = Path.join(priv_dir, "results/dev_gi.json")

results = case File.read(file_path) do
{:ok, file_content} ->
case Jason.decode(file_content) do
{:ok, data} -> data
{:error, _} -> %{"Questions" => []}
end
surveys = [
%{
id: "dev_gi",
name: "👩‍💻 Developers GI",
path: Path.join(priv_dir, "results/dev_gi.json")
},
%{
id: "companies",
name: "🏢 Companies",
path: Path.join(priv_dir, "results/companies.json")
},
%{
id: "academia",
name: "🎓 Academia",
path: Path.join(priv_dir, "results/academia.json")
}
]

{:error, _} ->
%{"Questions" => []}
end

{:ok, assign(socket, results: results, page_title: "Results")}
{:ok,
assign(socket,
surveys: surveys,
page_title: "Results",
active_survey_id: nil,
results: %{}
)}
end

def render(assigns) do
~H"""
<!-- Header -->
<div>
<div class="py-4">
<div class="text-center mb-8">
<h1 class="text-4xl md:text-5xl font-extralight my-4">BEAM <%= @results["Name"]%> Survey <%= @results["Year"]%></h1>
<h1 class="text-4xl md:text-5xl font-extralight my-4">BEAM <%= @results["Name"]%> Survey <%= @results["Year"]%></h1>
<p class="text-lg mt-2 font-light ">
Explore the trends, challenges, and strengths of the BEAM ecosystem from <%= @results["TotalResponses"]%> participants.
</p>
</div>
<!-- Categories Section -->
<div class="text-center font-light my-8">
<div class="flex flex-wrap gap-6 justify-center">
<%= for {section, section_index} <- Enum.with_index(@results["Sections"]) do %>
<%= button_to_section(%{index: section_index, name: section["Name"]}) %>
<% end %>
</div>
<!-- Buttons to choose the survey -->
<div class="grid gap-4 md:grid-cols-3 grid-cols-1 text-center md:w-4/5 w-2/3 mx-auto">
<%= for survey <- @surveys do %>
<div class="flex flex-col h-fit">
<button
phx-click="show_survey_results"
phx-value-id={survey.id}
aria-controls={survey.id}
aria-expanded={survey.id == @active_survey_id}
class={
"toggle-btn py-2 px-4 transition duration-300 rounded-lg hover:bg-main-700 text-white mt-auto " <>
if survey.id == @active_survey_id, do: "text-white bg-main-700", else: "bg-main-400"
}
>
<%= survey.name %>
</button>
</div>
<% end %>
</div>
</div>
<!-- Sections & Questions-->
<div class="results-container space-y-8">
<%= for {section, section_index} <- Enum.with_index(@results["Sections"]) do %>
<div id={"section-#{section_index}"} class={"section-#{section_index} p-6"}>
<h3 class="text-2xl text-center font-semibold pb-3 mb-4">
<a href={"#section-#{section_index}"}>
<%= section["Name"] %>
</a>
</h3>
<%= if @active_survey_id != nil do%>
<!-- Survey Content -->
<div class="md:w-full w-5/6 mx-auto">
<!-- Categories Section -->
<div class="text-center font-light my-8 py-8 border-y border-gray-500">
<div class="flex flex-wrap gap-6 justify-center">
<%= for {section, section_index} <- Enum.with_index(@results["Sections"]) do %>
<%= button_to_section(%{index: section_index, name: section["Name"], active_survey_id: @active_survey_id}) %>
<% end %>
</div>
</div>
<%= for {question, question_index} <- Enum.with_index(section["Questions"]) do %>
<!-- Sections & Questions-->
<div class="results-container space-y-8">
<%= for {section, section_index} <- Enum.with_index(@results["Sections"]) do %>
<div class={" p-6"} id={"#{@active_survey_id}-section-#{section_index}"}>
<h3 class="text-2xl text-center font-semibold pb-3 mb-4">
<%= section["Name"] %>
</h3>
<.live_component
module={Question}
question={question}
id={"section_#{section_index}_question_#{question_index}"
}/>
<%= for {question, question_index} <- Enum.with_index(section["Questions"]) do %>
<.live_component
module={Question}
question={question}
id={"#{@active_survey_id}-section_#{section_index}_question_#{question_index}"
}/>
<% end %>
</div>
<% end %>
</div>
<% end %>
</div>
<!-- Final -->
<div class="m-4">
<h3 class="text-center text-light text-2xl m-4"> ✨ A Big Thank You to All Participants! 💌</h3>
</div>
<button
id="scrollToTopBtn"
class="fixed bottom-4 md:bottom-8 right-4 md:right-8 bg-main-500 text-white p-4 md:p-6 rounded-full shadow-lg hover:bg-main-700 focus:outline-none"
aria-label="Scroll to top"
phx-hook="ScrollToTop"
>
</button>
<%else%>
<!-- Thxs -->
<div class="m-8 bg-main-100 border-l-4 border-main-400 text-main-800 p-4 rounded-md shadow-sm">
<p>
<strong>Thank you 🙏</strong> - We want to extend our heartfelt gratitude to every one of you for taking the time and effort of fill the suverys.
We keep working to make the next ones better. ❤️💜💗
</p>
</div>
<%end%>
<p class="text-center">
We want to extend our heartfelt gratitude to each and every one of you for your invaluable input.
🙏 Your feedback is not just appreciated—it’s truly instrumental in guiding us as we continue to improve and evolve.
We are incredibly grateful for the time and effort you’ve invested. We keep working to make the next ones better. ❤️💜💗
</p>
</div>
"""
end

def button_to_section(assigns) do
~H"""
<a href={"#section-#{@index}"} class="bg-main-500 text-white py-2 px-4 rounded-md hover:bg-main-700 focus:outline-none focus:ring-2 focus:ring-main-400">
<a href={"##{@active_survey_id}-section-#{@index}"} class="bg-main-500 text-white py-2 px-4 rounded-md hover:bg-main-700 focus:outline-none focus:ring-2 focus:ring-main-400">
<%= @name %>
</a>
"""
end

def handle_event("show_survey_results", %{"id" => survey_id, "value" => _value}, socket) do
surveys = socket.assigns.surveys

# Find the survey with the matching ID
case Enum.find(surveys, fn s -> s.id == survey_id end) do
nil ->
# Survey not found
{:noreply,
socket
|> assign(results: nil, active_survey_id: nil)
|> put_flash(:error, "Survey not found.")}

survey ->
# Attempt to read and decode the survey file
case File.read(survey.path) do
{:ok, file_content} ->
case Jason.decode(file_content) do
{:ok, data} ->
{:noreply, assign(socket, results: data, active_survey_id: survey_id)}

{:error, _} ->
{:noreply,
socket
|> assign(results: nil, active_survey_id: nil)
|> put_flash(:error, "Failed to display the survey data.")}
end

{:error, _} ->
{:noreply,
socket
|> assign(results: nil, active_survey_id: nil)
|> put_flash(:error, "Failed to display the survey data.")}
end
end
end
end
Loading

0 comments on commit 21b217f

Please sign in to comment.