diff --git a/lib/atomic/activities.ex b/lib/atomic/activities.ex index f6e5704c6..77c8af685 100644 --- a/lib/atomic/activities.ex +++ b/lib/atomic/activities.ex @@ -300,6 +300,12 @@ defmodule Atomic.Activities do Repo.all(Enrollment) end + def list_enrollments(opts) when is_list(opts) do + Enrollment + |> apply_filters(opts) + |> Repo.all() + end + @doc """ Gets a single enrollment. diff --git a/lib/atomic/organizations/certificate.ex b/lib/atomic/organizations/certificate.ex new file mode 100644 index 000000000..2cc265af0 --- /dev/null +++ b/lib/atomic/organizations/certificate.ex @@ -0,0 +1,28 @@ +defmodule Atomic.Certificate do + @moduledoc false + use Atomic.Schema + + alias Atomic.Organizations.Organization + + @required_fields ~w(background title content background_color title_color content_color organization_color organization_id)a + + schema "certificates" do + field :title, :integer + field :background, :boolean, default: false + field :content, :string + field :background_color, :string + field :title_color, :string + field :content_color, :string + field :organization_color, :string + belongs_to :organization, Organization + + timestamps() + end + + @doc false + def changeset(certificate, attrs) do + certificate + |> cast(attrs, @required_fields) + |> validate_required(@required_fields) + end +end diff --git a/lib/atomic/organizations/organization.ex b/lib/atomic/organizations/organization.ex index 9db7602bb..079cbb46c 100644 --- a/lib/atomic/organizations/organization.ex +++ b/lib/atomic/organizations/organization.ex @@ -1,14 +1,14 @@ defmodule Atomic.Organizations.Organization do @moduledoc false use Atomic.Schema - alias Atomic.Accounts.User + alias Atomic.Certificate alias Atomic.Location alias Atomic.Organizations.{Announcement, Department, Membership, Partner} alias Atomic.Uploaders @required_fields ~w(name long_name description)a - @optional_fields ~w()a + @optional_fields ~w(certificate_template_id)a @derive { Flop.Schema, @@ -45,6 +45,8 @@ defmodule Atomic.Organizations.Organization do many_to_many :users, User, join_through: Membership + belongs_to :certificate_template, Certificate + timestamps() end diff --git a/lib/atomic/quantum/certificate_delivery.ex b/lib/atomic/quantum/certificate_delivery.ex index 373f7d621..a04b116e5 100644 --- a/lib/atomic/quantum/certificate_delivery.ex +++ b/lib/atomic/quantum/certificate_delivery.ex @@ -65,11 +65,11 @@ defmodule Atomic.Quantum.CertificateDelivery do # It uses `wkhtmltopdf` to build it from an HTML template, which # is rendered beforehand. - defp generate_certificate( - %Enrollment{} = enrollment, - %Activity{} = activity, - %Organization{} = organization - ) do + def generate_certificate( + %Enrollment{} = enrollment, + %Activity{} = activity, + %Organization{} = organization + ) do # Create the string corresponding to the HTML to convert # to a PDF Phoenix.View.render_to_string(AtomicWeb.PDFView, "activity_certificate.html", @@ -96,6 +96,39 @@ defmodule Atomic.Quantum.CertificateDelivery do ) end + def generate_certificate( + %Enrollment{} = enrollment, + %Activity{} = activity, + %Organization{} = organization, + certificate_options \\ %{} + ) do + # Create the string corresponding to the HTML to convert + # to a PDF + Phoenix.View.render_to_string(AtomicWeb.PDFView, "activity_certificate.html", + enrollment: enrollment, + activity: activity, + organization: organization, + certificate_options: certificate_options + ) + |> PdfGenerator.generate( + delete_temporary: true, + page_size: "A4", + filename: "certificate_#{enrollment.id}", + shell_params: [ + "--margin-top", + "0", + "--margin-left", + "0", + "--margin-right", + "0", + "--margin-bottom", + "0", + "-O", + "landscape" + ] + ) + end + # Builds the query to determine the activities to consider for certificate # delivery. diff --git a/lib/atomic_web/live/organization_live/certificate_live/index.ex b/lib/atomic_web/live/organization_live/certificate_live/index.ex new file mode 100644 index 000000000..b6e374a0e --- /dev/null +++ b/lib/atomic_web/live/organization_live/certificate_live/index.ex @@ -0,0 +1,144 @@ +defmodule AtomicWeb.OrganizationLive.CertificateLive.Index do + use AtomicWeb, :live_view + + alias Atomic.Activities + alias Atomic.Certificate + alias Atomic.Organizations + import AtomicWeb.Components.Forms + + @impl true + def mount(_params, _session, socket) do + certificate_options = %{ + background: true, + title: 62, + content: "Para os devidos efeitos, certifica-se que participou na atividade", + background_color: "#ffffff", + title_color: "#fb923c", + content_color: "#000000" + } + + {:ok, assign(socket, certificate_options: certificate_options)} + end + + @impl true + def handle_params(%{"organization_id" => organization_id} = params, _url, socket) do + activities = list_activities(organization_id) + organization = Organizations.get_organization!(organization_id) + + default_options = %{ + background: true, + title: 62, + content: "Para os devidos efeitos, certifica-se que participou na atividade", + background_color: "#ffffff", + title_color: "#fb923c", + content_color: "#000000" + } + + certificate_options = default_options + + changeset = + Certificate.changeset(%Certificate{}, %{ + organization_id: organization_id, + background: default_options.background, + title: default_options.title, + content: default_options.content, + background_color: default_options.background_color, + title_color: default_options.title_color, + content_color: default_options.content_color + }) + + certificate = %Certificate{} + + {:noreply, + socket + |> assign(:page_title, gettext("Certificate")) + |> assign(:current_page, :certificate) + |> assign(:changeset, changeset) + |> assign(:certificate, certificate) + |> assign(:activities, activities) + |> assign(:organization, organization) + |> assign(:certificate_options, certificate_options) + |> assign(:params, params)} + end + + @impl true + def handle_event("validate", %{"certificate" => certificate_params}, socket) do + certificate_options = extract_certificate_options(certificate_params) + + changeset = + (socket.assigns.certificate || %Certificate{}) + |> Certificate.changeset( + Map.put(certificate_params, "organization_id", socket.assigns.organization.id) + ) + |> Map.put(:action, :validate) + + {:noreply, + socket + |> assign(:changeset, changeset) + |> assign(:certificate_options, certificate_options)} + end + + @impl true + def handle_event("save", _params, socket) do + organization_id = socket.assigns.organization.id + + certificate_params = %{ + "organization_id" => organization_id, + "background" => socket.assigns.certificate_options.background, + "title" => socket.assigns.certificate_options.title, + "content" => socket.assigns.certificate_options.content, + "background_color" => socket.assigns.certificate_options.background_color, + "title_color" => socket.assigns.certificate_options.title_color, + "content_color" => socket.assigns.certificate_options.content_color + } + + case %Certificate{} |> Certificate.changeset(certificate_params) |> Atomic.Repo.insert() do + {:ok, _certificate} -> + case Organizations.update_organization(socket.assigns.organization, %{ + certificate_template_id: socket.assigns.certificate.id + }) do + {:ok, _organization} -> + {:noreply, + socket + |> put_flash(:info, "Certificate saved and organization updated successfully!")} + + {:error, %Ecto.Changeset{} = _changeset} -> + {:noreply, + socket |> put_flash(:error, "Certificate saved, but failed to update organization")} + end + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, + socket |> assign(:changeset, changeset) |> put_flash(:error, "Error saving certificate")} + end + end + + defp list_activities(organization_id) do + case Activities.list_activities_by_organization_id(organization_id) do + {:ok, {activities, _meta}} -> activities + {:error, _flop} -> [] + end + end + + defp extract_certificate_options(params) do + %{ + background: Map.get(params, "background") == "true", + title: parse_integer(Map.get(params, "title"), 62), + content: + Map.get(params, "content") || + "Para os devidos efeitos, certifica-se que participou na atividade", + background_color: Map.get(params, "background_color") || "#ffffff", + title_color: Map.get(params, "title_color") || "#fb923c", + content_color: Map.get(params, "content_color") || "#000000" + } + end + + defp parse_integer(value, default) when is_binary(value) do + case Integer.parse(value) do + {int, _} -> int + :error -> default + end + end + + defp parse_integer(_, default), do: default +end diff --git a/lib/atomic_web/live/organization_live/certificate_live/index.html.heex b/lib/atomic_web/live/organization_live/certificate_live/index.html.heex new file mode 100644 index 000000000..e29c30cb4 --- /dev/null +++ b/lib/atomic_web/live/organization_live/certificate_live/index.html.heex @@ -0,0 +1,27 @@ +<.page title="Certificate"> + <:actions> + <.button size={:md} color={:white} icon="hero-cube" type="button" phx-click="save"> + {gettext("Save Certificate")} + + + + <.form :let={f} for={@changeset} id="certificate-form" phx-change="validate" class="flex flex-row items-start gap-8"> +
+ <%= Map.get(@certificate_options, :content, "Para os devidos efeitos, certifica-se que participou na atividade") %> +
+ Nome ++ Atividade, organizada pelo(a) <%= @organization.name %>. +
+ + ++ Este certificado foi gerado automaticamente pela plataforma open-source de gestão de núcleos Atomic, sendo da exclusiva responsabilidade do Centro de Estudantes de Engenharia de Informática da Universidade do Minho, com a devida autorização da organização dinamizadora da atividade. +
+