From 735069f182606584a6d602d2d1bf1b3f50ee541c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Rodrigues?= Date: Sat, 15 Jul 2023 21:33:13 +0100 Subject: [PATCH 01/26] Add layout --- config/config.exs | 3 + lib/atomic/activities.ex | 13 + lib/atomic/organizations.ex | 7 + lib/atomic_web.ex | 4 +- lib/atomic_web/config.ex | 67 +++++ lib/atomic_web/live/activity_live/edit.ex | 13 + lib/atomic_web/live/activity_live/index.ex | 26 +- .../live/activity_live/index.html.heex | 120 ++++++-- lib/atomic_web/live/activity_live/new.ex | 13 + lib/atomic_web/live/activity_live/show.ex | 28 +- .../live/activity_live/show.html.heex | 264 ++++++++++++------ lib/atomic_web/live/speaker_live/index.ex | 13 +- lib/atomic_web/live/speaker_live/show.ex | 13 + lib/atomic_web/router.ex | 4 +- .../templates/layout/live.html.heex | 224 ++++++++++++++- .../templates/layout/root.html.heex | 20 +- lib/atomic_web/views/utils_view.ex | 112 ++++++++ mix.exs | 1 + mix.lock | 1 + priv/repo/seeds/activities.exs | 47 ++-- .../controllers/page_controller_test.exs | 2 + .../user_registration_controller_test.exs | 5 +- .../user_session_controller_test.exs | 3 +- test/atomic_web/live/department_live_test.exs | 3 - .../live/organization_live_test.exs | 117 -------- test/atomic_web/live/partner_live_test.exs | 109 -------- test/atomic_web/live/speaker_live_test.exs | 112 -------- 27 files changed, 828 insertions(+), 516 deletions(-) create mode 100644 lib/atomic_web/config.ex create mode 100644 lib/atomic_web/views/utils_view.ex delete mode 100644 test/atomic_web/live/department_live_test.exs delete mode 100644 test/atomic_web/live/organization_live_test.exs delete mode 100644 test/atomic_web/live/partner_live_test.exs delete mode 100644 test/atomic_web/live/speaker_live_test.exs diff --git a/config/config.exs b/config/config.exs index b9bbd203f..b859cc6c4 100644 --- a/config/config.exs +++ b/config/config.exs @@ -67,6 +67,9 @@ config :tailwind, cd: Path.expand("../assets", __DIR__) ] +config :icons, + collection: [Heroicons, Ionicons] + config :atomic, Atomic.Scheduler, jobs: [ # Runs every midnight: diff --git a/lib/atomic/activities.ex b/lib/atomic/activities.ex index bc9dc1222..69e8f9d8d 100644 --- a/lib/atomic/activities.ex +++ b/lib/atomic/activities.ex @@ -263,6 +263,19 @@ defmodule Atomic.Activities do end end + def get_user_enrollments(user) do + Enrollment + |> where(user_id: ^user.id) + |> Repo.all() + end + + def get_user_activities(user) do + enrollments = get_user_enrollments(user) + activities = for enrollment <- enrollments, do: get_activity!(enrollment.activity_id) + + activities |> Repo.preload([:enrollments, :activity_sessions, :speakers]) + end + @doc """ Creates an enrollment. diff --git a/lib/atomic/organizations.ex b/lib/atomic/organizations.ex index 26bb50761..70eaf3a79 100644 --- a/lib/atomic/organizations.ex +++ b/lib/atomic/organizations.ex @@ -293,6 +293,13 @@ defmodule Atomic.Organizations do |> Repo.preload(preloads) end + def get_membership_role!(user_id, organization_id) do + case Repo.get_by(Membership, user_id: user_id, organization_id: organization_id) do + nil -> nil + membership -> membership.role + end + end + @doc """ Creates an user organization. diff --git a/lib/atomic_web.ex b/lib/atomic_web.ex index 7eb7cdb5f..c08964aab 100644 --- a/lib/atomic_web.ex +++ b/lib/atomic_web.ex @@ -98,8 +98,10 @@ defmodule AtomicWeb do import AtomicWeb.ErrorHelpers import AtomicWeb.Gettext - import AtomicWeb.ViewUtils + import AtomicWeb.UtilsView alias AtomicWeb.Router.Helpers, as: Routes + + alias Icons.{Heroicons, Ionicons} end end diff --git a/lib/atomic_web/config.ex b/lib/atomic_web/config.ex new file mode 100644 index 000000000..f514b91f5 --- /dev/null +++ b/lib/atomic_web/config.ex @@ -0,0 +1,67 @@ +defmodule AtomicWeb.Config do + @moduledoc """ + Web configuration for our pages. + """ + alias AtomicWeb.Router.Helpers, as: Routes + + def pages(conn) do + [ + %{ + key: :activities, + title: "Home", + icon: :home, + url: Routes.activity_index_path(conn, :index), + tabs: [] + }, + %{ + key: :calendar, + title: "Calendar", + icon: :calendar, + url: "", + tabs: [] + }, + %{ + key: :departments, + title: "Departments", + icon: :cube, + url: Routes.department_index_path(conn, :index, :org), + tabs: [] + }, + %{ + key: :instructors, + title: "Activities", + icon: :academic_cap, + url: Routes.activity_index_path(conn, :index), + tabs: [] + }, + %{ + key: :partners, + title: "Partners", + icon: :user_group, + url: Routes.partner_index_path(conn, :index), + tabs: [] + }, + %{ + key: :memberships, + title: "Memberships", + icon: :user_add, + url: Routes.membership_index_path(conn, :index, :org), + tabs: [] + }, + %{ + key: :board, + title: "Board", + icon: :users, + url: Routes.board_index_path(conn, :index, :org), + tabs: [] + }, + %{ + key: :scanner, + title: "Scanner", + icon: :qrcode, + url: Routes.scanner_index_path(conn, :index), + tabs: [] + } + ] + end +end diff --git a/lib/atomic_web/live/activity_live/edit.ex b/lib/atomic_web/live/activity_live/edit.ex index 8f63b4003..e3b90161c 100644 --- a/lib/atomic_web/live/activity_live/edit.ex +++ b/lib/atomic_web/live/activity_live/edit.ex @@ -11,8 +11,21 @@ defmodule AtomicWeb.ActivityLive.Edit do @impl true def handle_params(%{"id" => id} = _params, _url, socket) do + entries = [ + %{ + name: gettext("Activities"), + route: Routes.activity_index_path(socket, :index) + }, + %{ + name: gettext("Edit Activity"), + route: Routes.activity_edit_path(socket, :edit, id) + } + ] + {:noreply, socket + |> assign(:breadcrumb_entries, entries) + |> assign(:current_page, :activities) |> assign(:page_title, gettext("Edit Activity")) |> assign( :activity, diff --git a/lib/atomic_web/live/activity_live/index.ex b/lib/atomic_web/live/activity_live/index.ex index 3dcf0321e..f015369f8 100644 --- a/lib/atomic_web/live/activity_live/index.ex +++ b/lib/atomic_web/live/activity_live/index.ex @@ -11,7 +11,18 @@ defmodule AtomicWeb.ActivityLive.Index do @impl true def handle_params(params, _url, socket) do - {:noreply, apply_action(socket, socket.assigns.live_action, params)} + entries = [ + %{ + name: gettext("Activities"), + route: Routes.activity_index_path(socket, :index) + } + ] + + {:noreply, + socket + |> assign(:current_page, :activities) + |> assign(:breadcrumb_entries, entries) + |> apply_action(socket.assigns.live_action, params)} end defp apply_action(socket, :edit, %{"id" => id}) do @@ -40,7 +51,18 @@ defmodule AtomicWeb.ActivityLive.Index do {:noreply, assign(socket, :activies, list_activities())} end + def handle_event("open-enrollments", _payload, socket) do + {:noreply, assign(socket, :activities, list_activities())} + end + + def handle_event("activities-enrolled", _payload, socket) do + user = socket.assigns.current_user + activities = Activities.get_user_activities(user) + + {:noreply, assign(socket, :activities, activities)} + end + defp list_activities do - Activities.list_activities(preloads: [:activity_sessions]) + Activities.list_activities(preloads: [:activity_sessions, :enrollments, :speakers]) end end diff --git a/lib/atomic_web/live/activity_live/index.html.heex b/lib/atomic_web/live/activity_live/index.html.heex index 2b959a11f..eacafb63d 100644 --- a/lib/atomic_web/live/activity_live/index.html.heex +++ b/lib/atomic_web/live/activity_live/index.html.heex @@ -1,32 +1,92 @@ -

Listing Activities

+
+
+
+
+
+
+

+ <%= gettext("Activities") %> +

+
+
+
+
+
+
diff --git a/lib/atomic_web/live/activity_live/new.ex b/lib/atomic_web/live/activity_live/new.ex index d20688024..c7fcb823c 100644 --- a/lib/atomic_web/live/activity_live/new.ex +++ b/lib/atomic_web/live/activity_live/new.ex @@ -12,9 +12,22 @@ defmodule AtomicWeb.ActivityLive.New do @impl true def handle_params(_params, _url, socket) do + entries = [ + %{ + name: gettext("Activities"), + route: Routes.activity_index_path(socket, :index) + }, + %{ + name: gettext("New Activity"), + route: Routes.activity_new_path(socket, :new) + } + ] + {:noreply, socket |> assign(:page_title, gettext("New Activity")) + |> assign(:breadcrumb_entries, entries) + |> assign(:current_page, :activities) |> assign(:activity, %Activity{ activity_sessions: [%Session{}], speakers: [] diff --git a/lib/atomic_web/live/activity_live/show.ex b/lib/atomic_web/live/activity_live/show.ex index 7f2b21ccc..da92e20df 100644 --- a/lib/atomic_web/live/activity_live/show.ex +++ b/lib/atomic_web/live/activity_live/show.ex @@ -2,6 +2,7 @@ defmodule AtomicWeb.ActivityLive.Show do use AtomicWeb, :live_view alias Atomic.Activities + alias Atomic.Organizations @impl true def mount(%{"id" => id}, _session, socket) do @@ -17,10 +18,23 @@ defmodule AtomicWeb.ActivityLive.Show do def handle_params(%{"id" => id}, _, socket) do activity = Activities.get_activity!(id, [:activity_sessions, :departments, :speakers]) + entries = [ + %{ + name: gettext("Activities"), + route: Routes.activity_index_path(socket, :index) + }, + %{ + name: activity.title, + route: Routes.activity_show_path(socket, :show, activity) + } + ] + {:noreply, socket |> assign(:enrolled?, Activities.is_user_enrolled?(activity, socket.assigns.current_user)) |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:breadcrumb_entries, entries) + |> assign(:current_page, :activities) |> assign(:activity, %{activity | enrolled: Activities.get_total_enrolled(activity)})} end @@ -54,13 +68,12 @@ defmodule AtomicWeb.ActivityLive.Show do {:noreply, socket |> put_flash(:success, gettext("Unenrolled successufully!")) - |> set_enrolled(activity, current_user)} + |> assign(:enrolled?, false)} {_, nil} -> {:noreply, socket - |> put_flash(:error, gettext("Unable to unenroll")) - |> set_enrolled(activity, current_user)} + |> put_flash(:error, gettext("Unable to unenroll"))} end end @@ -91,8 +104,8 @@ defmodule AtomicWeb.ActivityLive.Show do assign(socket, :activity, %{activity | enrolled: Activities.get_total_enrolled(activity)})} end - defp draw_qr_code(session, user, _socket) do - internal_route = "/redeem/#{session.activity_id}/#{user.id}/confirm" + defp draw_qr_code(activity, user, _socket) do + internal_route = "/redeem/#{activity.id}/#{user.id}/confirm" url = build_url() <> internal_route @@ -117,4 +130,9 @@ defmodule AtomicWeb.ActivityLive.Show do "https://#{Application.fetch_env(:atomic, AtomicWeb.Endpoint)[:url][:host]}" end end + + def is_admin?(user, activity) do + department = activity.departments |> Enum.at(0) + Organizations.get_membership_role!(user.id, department.organization_id) in [:admin, :owner] + end end diff --git a/lib/atomic_web/live/activity_live/show.html.heex b/lib/atomic_web/live/activity_live/show.html.heex index fa94dcc61..573d61e2f 100644 --- a/lib/atomic_web/live/activity_live/show.html.heex +++ b/lib/atomic_web/live/activity_live/show.html.heex @@ -1,99 +1,191 @@ -

Show Activity

- <%= if @live_action in [:edit] do %> <.modal return_to={Routes.activity_show_path(@socket, :show, @activity)}> <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @activity)} /> <% end %> -
    -
  • - Title: - <%= @activity.title %> -
  • - -
  • - Description: - <%= @activity.description %> -
  • - -
  • - Minimum entries: - <%= @activity.minimum_entries %> -
  • +<%= if @live_action in [:edit] do %> + <.modal return_to={Routes.activity_show_path(@socket, :show, @activity)}> + <.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @activity)} /> + +<% end %> -
  • - Maximum entries: - <%= @activity.enrolled %> / <%= @activity.maximum_entries %> -
  • +
    +
    +
    +
    +
    +
    +

    + <%= @activity.title %> +

    +
    +
    +
    -
  • - Departments: - <%= for department <- @activity.departments do %> -
      -
    • - <%= department.name %> -
    • -
    - <% end %> -
  • +
    +
    +
    +
    +
    + <%= gettext("Preview activity") %> +
    +
    + <%= gettext("Check here every detail of this activity") %> +
    +
    -
  • - Speakers: - <%= for speaker <- @activity.speakers do %> -
      -
    • - <%= speaker.name %> -
    • -
    - <% end %> -
  • +
    +
    + <%= gettext("Maximum Entries") %> +
    +
    + +
    + <%= @activity.enrolled %> / <%= @activity.maximum_entries %> +
    +
    +
    +
    +
    + <%= gettext("Minimum Entries") %> +
    +
    +
    + +
    + <%= @activity.minimum_entries %> +
    +
    +
    +
    -
  • - Sessions: - <%= for {session, index} <- Enum.with_index(@activity.activity_sessions) do %> -
      - <%= "Session #{index}" %> -
    • - Start: - <%= session.start %> -
    • -
    • - Finish: - <%= session.finish %> -
    • -
    • - Location: - <%= session.location.name %> -
    • -
    • - url: - <%= if session.location.url do %> - <%= session.location.url %> - <% end %> -
    • -
      - <%= draw_qr_code(session, @current_user, @socket) |> raw %> +
      +
      +
      + <%= gettext("Sessions") %> +
      +
      +
      + <%= for session <- @activity.activity_sessions do %> +
      +
      +
      + +

      + <%= display_date(session.start) %> +

      +
      +
      +
      + +

      + <%= display_time(session.start) %> - <%= display_time(session.finish) %> +

      +
      +
      + <%= if (@current_user.role != :student || @enrolled?) && session.location do %> + + <%= live_redirect to: session.location.url, data: [confirm: gettext("Are you sure?")], class: "pl-1.5 text-blue-500" do %> + <%= session.location.name %> + <% end %> + <% else %> + <%= if session.location do %> +
      + <%= session.location.name %> +
      + <% end %> + <% end %> +
      +
      + <% end %> +
      +
      +
      +
      + <%= gettext("Description") %> +
      +
      + <%= @activity.description %> +
      +
      +
      +
      + <%= if @activity.speakers != [] do %> + <%= gettext("Speaker") %> + <%= for speaker <- @activity.speakers do %> +
      +
      +
      + + + <%= extract_initials(speaker.name) %> + + + <%= live_redirect to: Routes.speaker_show_path(@socket, :show, speaker), class: "text-md text-blue-500" do %> + <%= extract_first_last_name(speaker.name) %> + <% end %> +
      +
      + <% end %> + <% end %> +
      +
      +
      + <%= if @enrolled? do %> +
      + <%= gettext("Your already enrolled in the activity, below is your QRCode") %> +
      +
      + <%= draw_qr_code(@activity, @current_user, @socket) |> raw %> +
      +
      + + <%= gettext("If you can't go please") %> + + +
      + <% else %> +
      +
      + <%= gettext("You are not enrolled in this activity") %> +
      + +
      + <% end %> +
      +
  • -
- <% end %> - - +
+
+
+
-<%= if @current_user.role == :student do %> - - <%= if @enrolled? do %> - - <% else %> - - <% end %> +
+ <%= if @current_user.role in [:admin] or is_admin?(@current_user, @activity) do %> + + <%= live_patch("Edit", to: Routes.activity_edit_path(@socket, :edit, @activity), class: "button") %> + + + <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: @activity.id, data: [confirm: "Are you sure?"]) %> + + <% end %> + + <%= live_redirect("Back", to: Routes.activity_index_path(@socket, :index)) %> -<% end %> -<%= if @current_user.role in [:admin, :staff] do %> - <%= live_patch("Edit", to: Routes.activity_edit_path(@socket, :edit, @activity), class: "button") %> | <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: @activity.id, data: [confirm: "Are you sure?"]) %> | - <%= live_redirect("Back", to: Routes.activity_index_path(@socket, :index)) %> -<% end %> +
diff --git a/lib/atomic_web/live/speaker_live/index.ex b/lib/atomic_web/live/speaker_live/index.ex index 2157c7bfe..85bc39238 100644 --- a/lib/atomic_web/live/speaker_live/index.ex +++ b/lib/atomic_web/live/speaker_live/index.ex @@ -11,7 +11,18 @@ defmodule AtomicWeb.SpeakerLive.Index do @impl true def handle_params(params, _url, socket) do - {:noreply, apply_action(socket, socket.assigns.live_action, params)} + entries = [ + %{ + name: gettext("Speakers"), + route: Routes.speaker_index_path(socket, :index) + } + ] + + {:noreply, + socket + |> assign(:current_page, :speakers) + |> assign(:breadcrumb_entries, entries) + |> apply_action(socket.assigns.live_action, params)} end defp apply_action(socket, :edit, %{"id" => id}) do diff --git a/lib/atomic_web/live/speaker_live/show.ex b/lib/atomic_web/live/speaker_live/show.ex index c134819c2..eb5b4b47d 100644 --- a/lib/atomic_web/live/speaker_live/show.ex +++ b/lib/atomic_web/live/speaker_live/show.ex @@ -10,9 +10,22 @@ defmodule AtomicWeb.SpeakerLive.Show do @impl true def handle_params(%{"id" => id}, _, socket) do + entries = [ + %{ + name: gettext("Speakers"), + route: Routes.speaker_index_path(socket, :index) + }, + %{ + name: gettext("Show"), + route: Routes.speaker_show_path(socket, :show, id) + } + ] + {:noreply, socket |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:current_page, :speakers) + |> assign(:breadcrumb_entries, entries) |> assign(:speaker, Activities.get_speaker!(id))} end diff --git a/lib/atomic_web/router.ex b/lib/atomic_web/router.ex index 5313a2eb0..04f31502e 100644 --- a/lib/atomic_web/router.ex +++ b/lib/atomic_web/router.ex @@ -19,16 +19,14 @@ defmodule AtomicWeb.Router do scope "/", AtomicWeb do pipe_through :browser - - live "/", ActivityLive.Index, :index end scope "/", AtomicWeb do pipe_through [:browser, :require_authenticated_user] live_session :logged_in, on_mount: [{AtomicWeb.Hooks, :current_user}] do + live "/", ActivityLive.Index, :index live "/scanner", ScannerLive.Index, :index - live "/activities", ActivityLive.Index, :index live "/activities/new", ActivityLive.New, :new live "/activities/:id/edit", ActivityLive.Edit, :edit diff --git a/lib/atomic_web/templates/layout/live.html.heex b/lib/atomic_web/templates/layout/live.html.heex index 2049b86f1..959330f84 100644 --- a/lib/atomic_web/templates/layout/live.html.heex +++ b/lib/atomic_web/templates/layout/live.html.heex @@ -1,7 +1,221 @@ -
- +
+
+
+
+
+
+ +
+ +
- +
+
+ + <%= for page <- AtomicWeb.Config.pages(@socket) do %> + + <% end %> +
+ + + + + + +
+ + +
+ <%= @inner_content %> +
+
+ + diff --git a/lib/atomic_web/templates/layout/root.html.heex b/lib/atomic_web/templates/layout/root.html.heex index c03c73b66..805064944 100644 --- a/lib/atomic_web/templates/layout/root.html.heex +++ b/lib/atomic_web/templates/layout/root.html.heex @@ -1,30 +1,16 @@ - + <%= csrf_meta_tag() %> - <%= live_title_tag(assigns[:page_title] || "Atomic", suffix: " · Phoenix Framework") %> + <%= live_title_tag(assigns[:page_title] || "", suffix: " · Atomic") %> - -
-
- -
-
+ <%= @inner_content %> diff --git a/lib/atomic_web/views/utils_view.ex b/lib/atomic_web/views/utils_view.ex new file mode 100644 index 000000000..61111374f --- /dev/null +++ b/lib/atomic_web/views/utils_view.ex @@ -0,0 +1,112 @@ +defmodule AtomicWeb.UtilsView do + @moduledoc """ + Utility functions to be used on all views. + """ + use Phoenix.HTML + + import AtomicWeb.Gettext + + alias Timex.Format.DateTime.Formatters.Relative + + @spec extract_initials(nil | String.t()) :: String.t() + def extract_initials(nil), do: "" + + def extract_initials(name) do + initials = + name + |> String.upcase() + |> String.split(" ") + |> Enum.map(&String.slice(&1, 0, 1)) + |> Enum.filter(&String.match?(&1, ~r/^\p{L}$/u)) + + case length(initials) do + 0 -> "" + 1 -> hd(initials) + _ -> List.first(initials) <> List.last(initials) + end + end + + @spec extract_first_last_name(nil | String.t()) :: String.t() + def extract_first_last_name(nil), do: "" + + def extract_first_last_name(name) do + names = + name + |> String.split(" ") + |> Enum.filter(&String.match?(String.slice(&1, 0, 1), ~r/^\p{L}$/u)) + |> Enum.map(&String.capitalize/1) + + case length(names) do + 0 -> "" + 1 -> hd(names) + _ -> List.first(names) <> " " <> List.last(names) + end + end + + def relative_datetime(nil), do: "" + + def relative_datetime(""), do: "" + + def relative_datetime(datetime) do + Relative.lformat!(datetime, "{relative}", Gettext.get_locale()) + end + + def display_date(nil), do: "" + + def display_date(""), do: "" + + def display_date(date) when is_binary(date) do + date + |> Timex.parse!("%FT%H:%M", :strftime) + |> Timex.format!("{0D}-{0M}-{YYYY}") + end + + def display_date(date) do + Timex.format!(date, "{0D}-{0M}-{YYYY}") + end + + def display_time(nil), do: "" + + def display_time(""), do: "" + + def display_time(date) when is_binary(date) do + date + |> Timex.parse!("%FT%H:%M", :strftime) + |> Timex.format!("{0D}-{0M}-{YYYY}") + end + + def display_time(date) do + date + |> Timex.format!("{h24}:{m}") + end + + def class_list(items) do + items + |> Enum.reject(&(elem(&1, 1) == false)) + |> Enum.map_join(" ", &elem(&1, 0)) + end + + def col_start(col) do + case col do + 1 -> "col-start-1" + 2 -> "col-start-2" + 3 -> "col-start-3" + 4 -> "col-start-4" + 5 -> "col-start-5" + 6 -> "col-start-6" + 7 -> "col-start-7" + _ -> "col-start-0" + end + end + + def build_path(current_path, params) do + current_path + |> URI.parse() + |> Map.put(:query, URI.encode_query(params)) + |> URI.to_string() + end + + def error_to_string(:too_large), do: gettext("Too large") + def error_to_string(:not_accepted), do: gettext("You have selected an unacceptable file type") + def error_to_string(:too_many_files), do: gettext("You have selected too many files") +end diff --git a/mix.exs b/mix.exs index 90f1910b0..ee826315f 100644 --- a/mix.exs +++ b/mix.exs @@ -59,6 +59,7 @@ defmodule Atomic.MixProject do # frontend {:tailwind, "~> 0.1", runtime: Mix.env() == :dev}, {:flop, "~> 0.17.0"}, + {:icons, "~> 0.7.1"}, {:esbuild, "~> 0.4", runtime: Mix.env() == :dev}, # monitoring diff --git a/mix.lock b/mix.lock index 96258159b..974c414f4 100644 --- a/mix.lock +++ b/mix.lock @@ -26,6 +26,7 @@ "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "icons": {:hex, :icons, "0.7.1", "49f4c9b81de8bb6c9a4b1477ba14f4628e04487eef87b435513873f48372a6c2", [:mix], [{:phoenix_html, "~> 3.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "a0ffd858513b352e4236926013a3c9678fcf94e7767d177dbbfbe3250a6b4aee"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, diff --git a/priv/repo/seeds/activities.exs b/priv/repo/seeds/activities.exs index 5316b8318..62cec3f29 100644 --- a/priv/repo/seeds/activities.exs +++ b/priv/repo/seeds/activities.exs @@ -1,14 +1,17 @@ defmodule Atomic.Repo.Seeds.Activities do + alias Atomic.Activities.ActivityDepartment alias Atomic.Repo alias Atomic.Accounts.User alias Atomic.Departments.Department + alias Atomic.Activities.ActivityDepartment alias Atomic.Organizations.Organization alias Atomic.Activities.{Activity, Enrollment, Location} def run do seed_activities() seed_enrollments() + seed_activity_departments() end def seed_activities() do @@ -36,10 +39,7 @@ defmodule Atomic.Repo.Seeds.Activities do location: location } ], - enrolled: 0, - departments: [ - Repo.get_by(Department, name: "Merchandise and Partnerships") |> Map.get(:id) - ] + enrolled: 0 } ) |> Repo.insert!() @@ -64,8 +64,7 @@ defmodule Atomic.Repo.Seeds.Activities do %{ user_id: 1 } - ], - departments: [Repo.get_by(Department, name: "Marketing and Content") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -90,8 +89,7 @@ defmodule Atomic.Repo.Seeds.Activities do %{ user_id: 1 } - ], - departments: [Repo.get_by(Department, name: "Recreative") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -116,8 +114,7 @@ defmodule Atomic.Repo.Seeds.Activities do %{ user_id: 1 } - ], - departments: [Repo.get_by(Department, name: "Pedagogical") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -141,8 +138,7 @@ defmodule Atomic.Repo.Seeds.Activities do %{ user_id: 1 } - ], - departments: [Repo.get_by(Department, name: "CAOS") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -162,10 +158,7 @@ defmodule Atomic.Repo.Seeds.Activities do location: location } ], - enrolled: 0, - departments: [ - Repo.get_by(Department, name: "Merchandise and Partnerships") |> Map.get(:id) - ] + enrolled: 0 } ) |> Repo.insert!() @@ -190,8 +183,7 @@ defmodule Atomic.Repo.Seeds.Activities do %{ user_id: 1 } - ], - departments: [Repo.get_by(Department, name: "Marketing and Content") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -216,8 +208,7 @@ defmodule Atomic.Repo.Seeds.Activities do %{ user_id: 1 } - ], - departments: [Repo.get_by(Department, name: "Recreative") |> Map.get(:id)] + ] } ) |> Repo.insert!() @@ -243,6 +234,22 @@ defmodule Atomic.Repo.Seeds.Activities do end end end + + def seed_activity_departments() do + case Repo.all(ActivityDepartment) do + [] -> + department = Repo.get_by(Department, name: "CAOS") + + for activity <- Repo.all(Activity) do + %ActivityDepartment{} + |> ActivityDepartment.changeset(%{ + activity_id: activity.id, + department_id: department.id + }) + |> Repo.insert!() + end + end + end end Atomic.Repo.Seeds.Activities.run() diff --git a/test/atomic_web/controllers/page_controller_test.exs b/test/atomic_web/controllers/page_controller_test.exs index ee513681a..0559aa7ea 100644 --- a/test/atomic_web/controllers/page_controller_test.exs +++ b/test/atomic_web/controllers/page_controller_test.exs @@ -1,6 +1,8 @@ defmodule AtomicWeb.PageControllerTest do use AtomicWeb.ConnCase + setup :register_and_log_in_user + test "GET /", %{conn: conn} do conn = get(conn, "/") assert html_response(conn, 200) =~ "Listing Activities" diff --git a/test/atomic_web/controllers/user_registration_controller_test.exs b/test/atomic_web/controllers/user_registration_controller_test.exs index 1b520f7c9..a60abd188 100644 --- a/test/atomic_web/controllers/user_registration_controller_test.exs +++ b/test/atomic_web/controllers/user_registration_controller_test.exs @@ -7,7 +7,7 @@ defmodule AtomicWeb.UserRegistrationControllerTest do response = html_response(conn, 200) assert response =~ "

Register

" assert response =~ "Log in" - assert response =~ "Register" + assert response =~ "Register" end test "redirects if already logged in", %{conn: conn} do @@ -38,8 +38,7 @@ defmodule AtomicWeb.UserRegistrationControllerTest do conn = get(conn, "/") response = html_response(conn, 200) - assert response =~ "Settings" - assert response =~ "Log out" + assert response =~ "Home" end end end diff --git a/test/atomic_web/controllers/user_session_controller_test.exs b/test/atomic_web/controllers/user_session_controller_test.exs index d79e729ed..24d057d77 100644 --- a/test/atomic_web/controllers/user_session_controller_test.exs +++ b/test/atomic_web/controllers/user_session_controller_test.exs @@ -36,8 +36,7 @@ defmodule AtomicWeb.UserSessionControllerTest do conn = get(conn, "/") response = html_response(conn, 200) assert response =~ user.email - assert response =~ "Settings" - assert response =~ "Log out" + assert response =~ "Home" end test "logs the user in with remember me", %{conn: conn, user: user} do diff --git a/test/atomic_web/live/department_live_test.exs b/test/atomic_web/live/department_live_test.exs deleted file mode 100644 index bda3e7a91..000000000 --- a/test/atomic_web/live/department_live_test.exs +++ /dev/null @@ -1,3 +0,0 @@ -defmodule AtomicWeb.DepartmentLiveTest do - use AtomicWeb.ConnCase -end diff --git a/test/atomic_web/live/organization_live_test.exs b/test/atomic_web/live/organization_live_test.exs deleted file mode 100644 index 125a7d4ae..000000000 --- a/test/atomic_web/live/organization_live_test.exs +++ /dev/null @@ -1,117 +0,0 @@ -defmodule AtomicWeb.OrganizationLiveTest do - use AtomicWeb.ConnCase - - import Phoenix.LiveViewTest - import Atomic.OrganizationsFixtures - - @create_attrs %{description: "some description", name: "some name"} - @update_attrs %{description: "some updated description", name: "some updated name"} - @invalid_attrs %{description: nil, name: nil} - - defp create_organization(_) do - organization = organization_fixture() - %{organization: organization} - end - - describe "Index" do - setup [:create_organization] - setup [:register_and_log_in_user] - - test "lists all organizations", %{conn: conn, organization: organization} do - {:ok, _index_live, html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert html =~ "Listing Organizations" - assert html =~ organization.description - end - - test "saves new organization", %{conn: conn} do - {:ok, index_live, _html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert index_live |> element("a", "New Organization") |> render_click() =~ - "New Organization" - - assert_patch(index_live, Routes.organization_index_path(conn, :new)) - - assert index_live - |> form("#organization-form", organization: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#organization-form", organization: @create_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.organization_index_path(conn, :index)) - - assert html =~ "Organization created successfully" - assert html =~ "some description" - end - - test "updates organization in listing", %{conn: conn, organization: organization} do - {:ok, index_live, _html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert index_live |> element("#organization-#{organization.id} a", "Edit") |> render_click() =~ - "Edit Organization" - - assert_patch(index_live, Routes.organization_index_path(conn, :edit, organization)) - - assert index_live - |> form("#organization-form", organization: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#organization-form", organization: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.organization_index_path(conn, :index)) - - assert html =~ "Organization updated successfully" - assert html =~ "some updated description" - end - - test "deletes organization in listing", %{conn: conn, organization: organization} do - {:ok, index_live, _html} = live(conn, Routes.organization_index_path(conn, :index)) - - assert index_live - |> element("#organization-#{organization.id} a", "Delete") - |> render_click() - - refute has_element?(index_live, "#organization-#{organization.id}") - end - end - - describe "Show" do - setup [:create_organization] - setup [:register_and_log_in_user] - - test "displays organization", %{conn: conn, organization: organization} do - {:ok, _show_live, html} = - live(conn, Routes.organization_show_path(conn, :show, organization)) - - assert html =~ "Show Organization" - assert html =~ organization.description - end - - test "updates organization within modal", %{conn: conn, organization: organization} do - {:ok, show_live, _html} = - live(conn, Routes.organization_show_path(conn, :show, organization)) - - assert show_live |> element("a", "Edit") |> render_click() =~ - "Edit Organization" - - assert_patch(show_live, Routes.organization_show_path(conn, :edit, organization)) - - assert show_live - |> form("#organization-form", organization: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - show_live - |> form("#organization-form", organization: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.organization_show_path(conn, :show, organization)) - - assert html =~ "Organization updated successfully" - assert html =~ "some updated description" - end - end -end diff --git a/test/atomic_web/live/partner_live_test.exs b/test/atomic_web/live/partner_live_test.exs deleted file mode 100644 index 342cec686..000000000 --- a/test/atomic_web/live/partner_live_test.exs +++ /dev/null @@ -1,109 +0,0 @@ -defmodule AtomicWeb.PartnerLiveTest do - use AtomicWeb.ConnCase - - import Phoenix.LiveViewTest - import Atomic.PartnershipsFixtures - - @create_attrs %{description: "some description", name: "some name"} - @update_attrs %{description: "some updated description", name: "some updated name"} - @invalid_attrs %{description: nil, name: nil} - - defp create_partner(_) do - partner = partner_fixture() - %{partner: partner} - end - - describe "Index" do - setup [:create_partner] - setup [:register_and_log_in_user] - - test "lists all partnerships", %{conn: conn, partner: partner} do - {:ok, _index_live, html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert html =~ "Listing Partnerships" - assert html =~ partner.description - end - - test "saves new partner", %{conn: conn} do - {:ok, index_live, _html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert index_live |> element("a", "New Partner") |> render_click() =~ - "New Partner" - - assert_patch(index_live, Routes.partner_index_path(conn, :new)) - - assert index_live - |> form("#partner-form", partner: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#partner-form", partner: @create_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.partner_index_path(conn, :index)) - - assert html =~ "some description" - end - - test "updates partner in listing", %{conn: conn, partner: partner} do - {:ok, index_live, _html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert index_live |> element("#partner-#{partner.id} a", "Edit") |> render_click() =~ - "Edit Partner" - - assert_patch(index_live, Routes.partner_index_path(conn, :edit, partner)) - - assert index_live - |> form("#partner-form", partner: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#partner-form", partner: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.partner_index_path(conn, :index)) - - assert html =~ "some updated description" - end - - test "deletes partner in listing", %{conn: conn, partner: partner} do - {:ok, index_live, _html} = live(conn, Routes.partner_index_path(conn, :index)) - - assert index_live |> element("#partner-#{partner.id} a", "Delete") |> render_click() - refute has_element?(index_live, "#partner-#{partner.id}") - end - end - - describe "Show" do - setup [:create_partner] - setup [:register_and_log_in_user] - - test "displays partner", %{conn: conn, partner: partner} do - {:ok, _show_live, html} = live(conn, Routes.partner_show_path(conn, :show, partner)) - - assert html =~ "Show Partner" - assert html =~ partner.description - end - - test "updates partner within modal", %{conn: conn, partner: partner} do - {:ok, show_live, _html} = live(conn, Routes.partner_show_path(conn, :show, partner)) - - assert show_live |> element("a", "Edit") |> render_click() =~ - "Edit Partner" - - assert_patch(show_live, Routes.partner_show_path(conn, :edit, partner)) - - assert show_live - |> form("#partner-form", partner: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - show_live - |> form("#partner-form", partner: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.partner_show_path(conn, :show, partner)) - - assert html =~ "some updated description" - end - end -end diff --git a/test/atomic_web/live/speaker_live_test.exs b/test/atomic_web/live/speaker_live_test.exs deleted file mode 100644 index 46a5f6200..000000000 --- a/test/atomic_web/live/speaker_live_test.exs +++ /dev/null @@ -1,112 +0,0 @@ -defmodule AtomicWeb.SpeakerLiveTest do - use AtomicWeb.ConnCase - - import Phoenix.LiveViewTest - import Atomic.ActivitiesFixtures - - @create_attrs %{bio: "some bio", name: "some name"} - @update_attrs %{bio: "some updated bio", name: "some updated name"} - @invalid_attrs %{bio: nil, name: nil} - - defp create_speaker(_) do - speaker = speaker_fixture() - %{speaker: speaker} - end - - describe "Index" do - setup [:create_speaker] - setup [:register_and_log_in_user] - - test "lists all speakers", %{conn: conn, speaker: speaker} do - {:ok, _index_live, html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert html =~ "Listing Speakers" - assert html =~ speaker.bio - end - - test "saves new speaker", %{conn: conn} do - {:ok, index_live, _html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert index_live |> element("a", "New Speaker") |> render_click() =~ - "New Speaker" - - assert_patch(index_live, Routes.speaker_index_path(conn, :new)) - - assert index_live - |> form("#speaker-form", speaker: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#speaker-form", speaker: @create_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.speaker_index_path(conn, :index)) - - assert html =~ "Speaker created successfully" - assert html =~ "some bio" - end - - test "updates speaker in listing", %{conn: conn, speaker: speaker} do - {:ok, index_live, _html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert index_live |> element("#speaker-#{speaker.id} a", "Edit") |> render_click() =~ - "Edit Speaker" - - assert_patch(index_live, Routes.speaker_index_path(conn, :edit, speaker)) - - assert index_live - |> form("#speaker-form", speaker: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - index_live - |> form("#speaker-form", speaker: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.speaker_index_path(conn, :index)) - - assert html =~ "Speaker updated successfully" - assert html =~ "some updated bio" - end - - test "deletes speaker in listing", %{conn: conn, speaker: speaker} do - {:ok, index_live, _html} = live(conn, Routes.speaker_index_path(conn, :index)) - - assert index_live |> element("#speaker-#{speaker.id} a", "Delete") |> render_click() - refute has_element?(index_live, "#speaker-#{speaker.id}") - end - end - - describe "Show" do - setup [:create_speaker] - setup [:register_and_log_in_user] - - test "displays speaker", %{conn: conn, speaker: speaker} do - {:ok, _show_live, html} = live(conn, Routes.speaker_show_path(conn, :show, speaker)) - - assert html =~ "Show Speaker" - assert html =~ speaker.bio - end - - test "updates speaker within modal", %{conn: conn, speaker: speaker} do - {:ok, show_live, _html} = live(conn, Routes.speaker_show_path(conn, :show, speaker)) - - assert show_live |> element("a", "Edit") |> render_click() =~ - "Edit Speaker" - - assert_patch(show_live, Routes.speaker_show_path(conn, :edit, speaker)) - - assert show_live - |> form("#speaker-form", speaker: @invalid_attrs) - |> render_change() =~ "can't be blank" - - {:ok, _, html} = - show_live - |> form("#speaker-form", speaker: @update_attrs) - |> render_submit() - |> follow_redirect(conn, Routes.speaker_show_path(conn, :show, speaker)) - - assert html =~ "Speaker updated successfully" - assert html =~ "some updated bio" - end - end -end From eec29b2429c9a3ff0ef9c739f5f10a2775c90069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Rodrigues?= Date: Fri, 21 Jul 2023 17:21:25 +0100 Subject: [PATCH 02/26] Implement suggestions --- lib/atomic/accounts.ex | 29 ++++ lib/atomic_web.ex | 2 +- lib/atomic_web/live/activity_live/index.ex | 1 + .../live/activity_live/index.html.heex | 6 +- lib/atomic_web/live/activity_live/show.ex | 1 + .../live/activity_live/show.html.heex | 6 +- .../templates/layout/live.html.heex | 6 +- lib/atomic_web/views/helpers.ex | 153 +++++++++++++----- lib/atomic_web/views/utils_view.ex | 112 ------------- test/atomic/utils_test.exs | 5 + 10 files changed, 162 insertions(+), 159 deletions(-) delete mode 100644 lib/atomic_web/views/utils_view.ex create mode 100644 test/atomic/utils_test.exs diff --git a/lib/atomic/accounts.ex b/lib/atomic/accounts.ex index 0fd35f28e..dd2877564 100644 --- a/lib/atomic/accounts.ex +++ b/lib/atomic/accounts.ex @@ -115,6 +115,35 @@ defmodule Atomic.Accounts do end end + @doc """ + Return the first and last name of a name. + + ## Examples + + iex> extract_first_last_name("John Doe") + "John Doe" + + iex> extract_first_last_name("John") + "John" + + iex> extract_first_last_name(nil) + "" + + """ + def extract_first_last_name(name) do + names = + name + |> String.split(" ") + |> Enum.filter(&String.match?(String.slice(&1, 0, 1), ~r/^\p{L}$/u)) + |> Enum.map(&String.capitalize/1) + + case length(names) do + 0 -> "" + 1 -> hd(names) + _ -> List.first(names) <> " " <> List.last(names) + end + end + @doc """ Returns an `%Ecto.Changeset{}` for tracking user changes. diff --git a/lib/atomic_web.ex b/lib/atomic_web.ex index c08964aab..06b431091 100644 --- a/lib/atomic_web.ex +++ b/lib/atomic_web.ex @@ -98,7 +98,7 @@ defmodule AtomicWeb do import AtomicWeb.ErrorHelpers import AtomicWeb.Gettext - import AtomicWeb.UtilsView + import AtomicWeb.ViewUtils alias AtomicWeb.Router.Helpers, as: Routes alias Icons.{Heroicons, Ionicons} diff --git a/lib/atomic_web/live/activity_live/index.ex b/lib/atomic_web/live/activity_live/index.ex index f015369f8..8c5bc62b0 100644 --- a/lib/atomic_web/live/activity_live/index.ex +++ b/lib/atomic_web/live/activity_live/index.ex @@ -1,6 +1,7 @@ defmodule AtomicWeb.ActivityLive.Index do use AtomicWeb, :live_view + alias Atomic.Accounts alias Atomic.Activities alias Atomic.Activities.Activity diff --git a/lib/atomic_web/live/activity_live/index.html.heex b/lib/atomic_web/live/activity_live/index.html.heex index eacafb63d..d5bd032e1 100644 --- a/lib/atomic_web/live/activity_live/index.html.heex +++ b/lib/atomic_web/live/activity_live/index.html.heex @@ -49,7 +49,7 @@

<%= if hd(activity.activity_sessions) do %> - <%= display_date(hd(activity.activity_sessions).start) %> + <%= AtomicWeb.ViewUtils.display_date(hd(activity.activity_sessions).start) %> <% end %>

<%= if not is_nil(hd(activity.activity_sessions).location) do %> @@ -71,11 +71,11 @@
- <%= extract_initials(speaker.name) %> + <%= Accounts.extract_initials(speaker.name) %>

- <%= extract_first_last_name(speaker.name) %> + <%= Accounts.extract_first_last_name(speaker.name) %>

<% end %> diff --git a/lib/atomic_web/live/activity_live/show.ex b/lib/atomic_web/live/activity_live/show.ex index da92e20df..48c2e1b49 100644 --- a/lib/atomic_web/live/activity_live/show.ex +++ b/lib/atomic_web/live/activity_live/show.ex @@ -1,6 +1,7 @@ defmodule AtomicWeb.ActivityLive.Show do use AtomicWeb, :live_view + alias Atomic.Accounts alias Atomic.Activities alias Atomic.Organizations diff --git a/lib/atomic_web/live/activity_live/show.html.heex b/lib/atomic_web/live/activity_live/show.html.heex index 573d61e2f..21647815a 100644 --- a/lib/atomic_web/live/activity_live/show.html.heex +++ b/lib/atomic_web/live/activity_live/show.html.heex @@ -79,7 +79,7 @@

- <%= display_date(session.start) %> + <%= AtomicWeb.ViewUtils.display_date(session.start) %>

@@ -125,11 +125,11 @@
- <%= extract_initials(speaker.name) %> + <%= Accounts.extract_initials(speaker.name) %> <%= live_redirect to: Routes.speaker_show_path(@socket, :show, speaker), class: "text-md text-blue-500" do %> - <%= extract_first_last_name(speaker.name) %> + <%= Accounts.extract_first_last_name(speaker.name) %> <% end %>
diff --git a/lib/atomic_web/templates/layout/live.html.heex b/lib/atomic_web/templates/layout/live.html.heex index 959330f84..e810c6c0e 100644 --- a/lib/atomic_web/templates/layout/live.html.heex +++ b/lib/atomic_web/templates/layout/live.html.heex @@ -18,7 +18,7 @@ - <%= extract_initials("CeSIUM") %> + <%= Atomic.Accounts.extract_initials("CeSIUM") %> @@ -60,7 +60,7 @@ - <%= extract_initials("NECC") %> + <%= Atomic.Accounts.extract_initials("NECC") %> @@ -178,7 +178,7 @@ <% end %> <%= live_redirect to: Routes.user_settings_path(@socket, :edit), class: "bg-zinc-200 flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-gray-900" do %> - <%= extract_initials(@current_user.email) %> + <%= Atomic.Accounts.extract_initials(@current_user.email) %> <% end %> diff --git a/lib/atomic_web/views/helpers.ex b/lib/atomic_web/views/helpers.ex index 475c2f5ff..e3db37af3 100644 --- a/lib/atomic_web/views/helpers.ex +++ b/lib/atomic_web/views/helpers.ex @@ -4,66 +4,145 @@ defmodule AtomicWeb.ViewUtils do """ use Phoenix.HTML + import AtomicWeb.Gettext + + alias Timex.Format.DateTime.Formatters.Relative + require Timex.Translator def frontend_url do Application.fetch_env!(:atomic, AtomicWeb.Endpoint)[:frontend_url] end - @doc """ - Display a user's name + @doc ~S""" + Returns a relative datetime string for the given datetime. + ## Examples - iex> display_name(%{first_name: "John", last_name: "Doe"}) - "John Doe" + + iex> relative_datetime(~N[2020-01-01 00:00:00]) + "3 years ago" + + iex> relative_datetime(~N[2023-01-01 00:00:00] |> Timex.shift(days: 1)) + "6 months ago" + """ - def display_name(user) do - "#{user.first_name} #{user.last_name}" + def relative_datetime(nil), do: "" + + def relative_datetime(""), do: "" + + def relative_datetime(datetime) do + Relative.lformat!(datetime, "{relative}", Gettext.get_locale()) end - @doc """ - Display a date in format "HH:MM" + @doc ~S""" + Returns a relative date string for the given date. + ## Examples - iex> display_time(~U[2018-01-01 00:00:00Z]) - "00:00" - iex> display_time(~U[2018-01-01 12:00:00Z]) - "12:00" - iex> display_time(~U[2018-01-01 23:59:00Z]) - "23:59" + + iex> display_date(~D[2020-01-01]) + "01-01-2020" + + iex> display_date(~D[2023-01-01]) + "01-01-2023" + """ - def display_time(%DateTime{} = datetime) do - hour = two_characters(datetime.hour) - minute = two_characters(datetime.minute) + def display_date(nil), do: "" + + def display_date(""), do: "" + + def display_date(date) when is_binary(date) do + date + |> Timex.parse!("%FT%H:%M", :strftime) + |> Timex.format!("{0D}-{0M}-{YYYY}") + end - "#{hour}:#{minute}" + def display_date(date) do + Timex.format!(date, "{0D}-{0M}-{YYYY}") end - @doc """ - Display a date in a given locale + @doc ~S""" + Returns a relative time string for the given time. + ## Examples - iex> display_date(~N[2021-03-10 02:27:07], "pt") - "Quarta-feira, 10 de Março de 2021" - iex> display_date(~N[2023-02-25 22:25:46], "en") - "Saturday, February 25, 2023" + + iex> display_time(~T[00:00:00]) + "00:00" + + iex> display_time(~T[23:59:59]) + "23:59" """ - def display_date(datetime, locale \\ "pt") + def display_time(nil), do: "" - def display_date(datetime, "pt" = locale) do - Timex.Translator.with_locale locale do - Timex.format!(datetime, "{WDfull}, {D} de {Mfull} de {YYYY}") - end + def display_time(""), do: "" + + def display_time(date) when is_binary(date) do + date + |> Timex.parse!("%FT%H:%M", :strftime) + |> Timex.format!("{0D}-{0M}-{YYYY}") end - def display_date(datetime, "en" = locale) do - Timex.Translator.with_locale locale do - Timex.format!(datetime, "{WDfull}, {Mfull} {D}, {YYYY}") - end + def display_time(date) do + date + |> Timex.format!("{h24}:{m}") end - defp two_characters(number) do - if number < 10 do - "0#{number}" + @doc ~S""" + Returns a list of first element from tuples where the second element is true + + ## Examples + + iex> class_list([{"col-start-1", true}, {"col-start-2", false}, {"col-start-3", true}]) + "col-start-1 col-start-3" + + iex> class_list([{"Math", true}, {"Physics", false}, {"Chemistry", false}]) + "Math" + """ + def class_list(items) do + items + |> Enum.reject(&(elem(&1, 1) == false)) + |> Enum.map_join(" ", &elem(&1, 0)) + end + + @doc ~S""" + Returns the class name for a given column + + ## Examples + + iex> col_start(1) + "col-start-1" + + iex> col_start(2) + "col-start-2" + + iex> col_start(0) + "col-start-0" + + iex> col_start(8) + "col-start-0" + """ + def col_start(col) do + if col in 1..7 do + "col-start-#{col}" else - number + "col-start-0" end end + + @doc ~S""" + Returns an error message for a given error + + ## Examples + + iex> error_to_string(:too_large) + "Too large" + + iex> error_to_string(:not_accepted) + "You have selected an unacceptable file type" + + iex> error_to_string(:too_many_files) + "You have selected too many files" + """ + def error_to_string(:too_large), do: gettext("Too large") + def error_to_string(:not_accepted), do: gettext("You have selected an unacceptable file type") + def error_to_string(:too_many_files), do: gettext("You have selected too many files") end diff --git a/lib/atomic_web/views/utils_view.ex b/lib/atomic_web/views/utils_view.ex deleted file mode 100644 index 61111374f..000000000 --- a/lib/atomic_web/views/utils_view.ex +++ /dev/null @@ -1,112 +0,0 @@ -defmodule AtomicWeb.UtilsView do - @moduledoc """ - Utility functions to be used on all views. - """ - use Phoenix.HTML - - import AtomicWeb.Gettext - - alias Timex.Format.DateTime.Formatters.Relative - - @spec extract_initials(nil | String.t()) :: String.t() - def extract_initials(nil), do: "" - - def extract_initials(name) do - initials = - name - |> String.upcase() - |> String.split(" ") - |> Enum.map(&String.slice(&1, 0, 1)) - |> Enum.filter(&String.match?(&1, ~r/^\p{L}$/u)) - - case length(initials) do - 0 -> "" - 1 -> hd(initials) - _ -> List.first(initials) <> List.last(initials) - end - end - - @spec extract_first_last_name(nil | String.t()) :: String.t() - def extract_first_last_name(nil), do: "" - - def extract_first_last_name(name) do - names = - name - |> String.split(" ") - |> Enum.filter(&String.match?(String.slice(&1, 0, 1), ~r/^\p{L}$/u)) - |> Enum.map(&String.capitalize/1) - - case length(names) do - 0 -> "" - 1 -> hd(names) - _ -> List.first(names) <> " " <> List.last(names) - end - end - - def relative_datetime(nil), do: "" - - def relative_datetime(""), do: "" - - def relative_datetime(datetime) do - Relative.lformat!(datetime, "{relative}", Gettext.get_locale()) - end - - def display_date(nil), do: "" - - def display_date(""), do: "" - - def display_date(date) when is_binary(date) do - date - |> Timex.parse!("%FT%H:%M", :strftime) - |> Timex.format!("{0D}-{0M}-{YYYY}") - end - - def display_date(date) do - Timex.format!(date, "{0D}-{0M}-{YYYY}") - end - - def display_time(nil), do: "" - - def display_time(""), do: "" - - def display_time(date) when is_binary(date) do - date - |> Timex.parse!("%FT%H:%M", :strftime) - |> Timex.format!("{0D}-{0M}-{YYYY}") - end - - def display_time(date) do - date - |> Timex.format!("{h24}:{m}") - end - - def class_list(items) do - items - |> Enum.reject(&(elem(&1, 1) == false)) - |> Enum.map_join(" ", &elem(&1, 0)) - end - - def col_start(col) do - case col do - 1 -> "col-start-1" - 2 -> "col-start-2" - 3 -> "col-start-3" - 4 -> "col-start-4" - 5 -> "col-start-5" - 6 -> "col-start-6" - 7 -> "col-start-7" - _ -> "col-start-0" - end - end - - def build_path(current_path, params) do - current_path - |> URI.parse() - |> Map.put(:query, URI.encode_query(params)) - |> URI.to_string() - end - - def error_to_string(:too_large), do: gettext("Too large") - def error_to_string(:not_accepted), do: gettext("You have selected an unacceptable file type") - def error_to_string(:too_many_files), do: gettext("You have selected too many files") -end diff --git a/test/atomic/utils_test.exs b/test/atomic/utils_test.exs new file mode 100644 index 000000000..a6d1b0526 --- /dev/null +++ b/test/atomic/utils_test.exs @@ -0,0 +1,5 @@ +defmodule Atomic.UtilsTest do + use ExUnit.Case, async: true + import AtomicWeb.ViewUtils + doctest AtomicWeb.ViewUtils +end From ab233a39539b4b09c53b8572409e30536067cf6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Rodrigues?= Date: Wed, 2 Aug 2023 16:09:01 +0100 Subject: [PATCH 03/26] Add some updates --- assets/tailwind.config.js | 6 +- config/config.exs | 9 +- config/dev.exs | 2 +- lib/atomic/activities.ex | 13 + lib/atomic/uploaders/partner_image.ex | 45 +++ lib/atomic_web/components/badges.ex | 78 ++++ lib/atomic_web/components/calendar.ex | 366 ++++++++++++++++++ lib/atomic_web/components/calendar/month.ex | 181 +++++++++ lib/atomic_web/components/calendar/week.ex | 225 +++++++++++ lib/atomic_web/config.ex | 2 +- lib/atomic_web/live/activity_live/index.ex | 7 +- .../live/activity_live/index.html.heex | 48 ++- lib/atomic_web/live/board_live/edit.ex | 9 +- lib/atomic_web/live/board_live/index.ex | 10 +- .../live/board_live/index.html.heex | 147 +++++-- lib/atomic_web/live/calendar_live/show.ex | 58 +++ .../live/calendar_live/show.html.heex | 8 + lib/atomic_web/live/department_live/index.ex | 7 +- .../live/department_live/index.html.heex | 87 +++-- lib/atomic_web/live/home_live/index.ex | 50 ++- lib/atomic_web/live/home_live/index.html.heex | 118 +++++- lib/atomic_web/live/hooks.ex | 12 +- lib/atomic_web/live/membership_live/edit.ex | 9 +- lib/atomic_web/live/membership_live/index.ex | 14 +- .../live/membership_live/index.html.heex | 86 ++-- lib/atomic_web/live/membership_live/show.ex | 9 +- .../live/organization_live/index.ex | 2 +- .../live/organization_live/index.html.heex | 2 - lib/atomic_web/live/partner_live/index.ex | 9 +- .../live/partner_live/index.html.heex | 81 ++-- lib/atomic_web/router.ex | 1 + lib/atomic_web/templates/error/404.html.heex | 2 +- .../templates/layout/live.html.heex | 6 +- .../templates/layout/root.html.heex | 2 +- lib/atomic_web/views/calendar_utils.ex | 63 +++ lib/atomic_web/views/helpers.ex | 7 + 36 files changed, 1597 insertions(+), 184 deletions(-) create mode 100644 lib/atomic/uploaders/partner_image.ex create mode 100644 lib/atomic_web/components/badges.ex create mode 100644 lib/atomic_web/components/calendar.ex create mode 100644 lib/atomic_web/components/calendar/month.ex create mode 100644 lib/atomic_web/components/calendar/week.ex create mode 100644 lib/atomic_web/live/calendar_live/show.ex create mode 100644 lib/atomic_web/live/calendar_live/show.html.heex create mode 100644 lib/atomic_web/views/calendar_utils.ex diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index cad21589a..080b95ea1 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -10,7 +10,11 @@ module.exports = { '../lib/*_web/**/*.*ex', ], theme: { - extend: {}, + extend: { + border: { + '1': '1px' + }, + }, }, plugins: [ require('@tailwindcss/forms'), diff --git a/config/config.exs b/config/config.exs index b859cc6c4..5dbedab33 100644 --- a/config/config.exs +++ b/config/config.exs @@ -8,7 +8,14 @@ import Config config :atomic, - ecto_repos: [Atomic.Repo] + ecto_repos: [Atomic.Repo], + generators: [binary_id: true], + owner: %{ + name: "Atomic", + time_zone: "Europe/Lisbon", + day_start: 0, + day_end: 24 + } # Configures the endpoint config :atomic, AtomicWeb.Endpoint, diff --git a/config/dev.exs b/config/dev.exs index 72c67d2f0..355b5f8fa 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -22,7 +22,7 @@ config :atomic, AtomicWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: 4000], check_origin: false, code_reloader: true, - debug_errors: false, + debug_errors: true, secret_key_base: "YGqNjmRjNv4pslzy4DB3Roi6xeyvS2/v/YXGn62mlMqCqjWsmu2UKbtNuNaYx5OO", watchers: [ # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args) diff --git a/lib/atomic/activities.ex b/lib/atomic/activities.ex index ef3b68daf..33020b2b5 100644 --- a/lib/atomic/activities.ex +++ b/lib/atomic/activities.ex @@ -31,6 +31,19 @@ defmodule Atomic.Activities do |> Repo.all() end + alias Atomic.Activities.Session + + def list_sessions_from_to(start, finish, opts) do + from(s in Session, + join: a in Activity, + on: s.activity_id == a.id, + where: s.start >= ^start and s.start <= ^finish, + order_by: [asc: s.start] + ) + |> apply_filters(opts) + |> Repo.all() + end + @doc """ Gets a single activity. diff --git a/lib/atomic/uploaders/partner_image.ex b/lib/atomic/uploaders/partner_image.ex new file mode 100644 index 000000000..e082fc320 --- /dev/null +++ b/lib/atomic/uploaders/partner_image.ex @@ -0,0 +1,45 @@ +defmodule Atomic.Uploaders.PartnerImage do + @moduledoc """ + ProductImage is used for product images. + """ + + use Waffle.Definition + use Waffle.Ecto.Definition + + alias Atomic.Partnerships.Partner + + @versions [:original, :medium, :thumb] + @extension_whitelist ~w(.jpg .jpeg .png) + + def validate({file, _}) do + file.file_name + |> Path.extname() + |> String.downcase() + |> then(&Enum.member?(@extension_whitelist, &1)) + |> case do + true -> :ok + false -> {:error, "Invalid file type. Only .jpg, .jpeg and .png are allowed."} + end + end + + def transform(:thumb, _) do + {:convert, "-strip -thumbnail 100x150^ -gravity center -extent 100x150 -format png", :png} + end + + def transform(:medium, _) do + {:convert, "-strip -thumbnail 400x600^ -gravity center -extent 400x600 -format png", :png} + end + + def filename(version, _) do + version + end + + def storage_dir(_version, {_file, %Partner{} = scope}) do + "uploads/store/#{scope.id}" + end + + # Provide a default URL if there hasn't been a file uploaded + def default_url(version) do + "uploads/store/partner_image_#{version}.png" + end +end diff --git a/lib/atomic_web/components/badges.ex b/lib/atomic_web/components/badges.ex new file mode 100644 index 000000000..9c11eab38 --- /dev/null +++ b/lib/atomic_web/components/badges.ex @@ -0,0 +1,78 @@ +defmodule AtomicWeb.Components.Badges do + @moduledoc false + use AtomicWeb, :component + + @colors ~w(gray red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose) + + def badge_dot(assigns) do + assigns = + assigns + |> assign_new(:color, fn -> "gray" end) + |> assign_new(:url, fn -> "#" end) + + background = + case assigns.color do + "gray" -> + "bg-gray-900" + + "red" -> + "bg-red-600" + + "purple" -> + "bg-purple-600" + end + + ~H""" + <%= live_redirect to: @url, class: "relative inline-flex items-center rounded-full border border-gray-300 px-3 py-0.5" do %> +
+ +
+
+ <%= render_slot(@inner_block) %> +
+ <% end %> + """ + end + + def badge(assigns) do + assigns = assign_new(assigns, :color, fn -> :gray end) + + ~H""" + + <%= @text %> + + """ + end + + defp get_color_classes(nil), do: "bg-neutral-100 text-neutral-800" + + defp get_color_classes(color) when is_atom(color) do + color + |> Atom.to_string() + |> get_color_classes() + end + + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity + defp get_color_classes(color) when color in @colors do + case color do + "gray" -> "bg-gray-100 text-gray-800" + "red" -> "bg-red-100 text-red-800" + "orange" -> "bg-orange-100 text-orange-800" + "amber" -> "bg-amber-100 text-amber-800" + "yellow" -> "bg-yellow-100 text-yellow-800" + "lime" -> "bg-lime-100 text-lime-800" + "green" -> "bg-green-100 text-green-800" + "emerald" -> "bg-emerald-100 text-emerald-800" + "teal" -> "bg-teal-100 text-teal-800" + "cyan" -> "bg-cyan-100 text-cyan-800" + "sky" -> "bg-sky-100 text-sky-800" + "blue" -> "bg-blue-100 text-blue-800" + "indigo" -> "bg-indigo-100 text-indigo-800" + "violet" -> "bg-violet-100 text-violet-800" + "purple" -> "bg-purple-100 text-purple-800" + "fuchsia" -> "bg-fuchsia-100 text-fuchsia-800" + "pink" -> "bg-pink-100 text-pink-800" + "rose" -> "bg-rose-100 text-rose-800" + end + end +end diff --git a/lib/atomic_web/components/calendar.ex b/lib/atomic_web/components/calendar.ex new file mode 100644 index 000000000..74afcedce --- /dev/null +++ b/lib/atomic_web/components/calendar.ex @@ -0,0 +1,366 @@ +defmodule AtomicWeb.Components.Calendar do + @moduledoc false + use AtomicWeb, :component + + alias Timex.Duration + + import AtomicWeb.CalendarUtils + import AtomicWeb.Components.CalendarMonth + import AtomicWeb.Components.CalendarWeek + + def calendar( + %{ + current_path: current_path, + params: params, + mode: mode, + time_zone: time_zone + } = assigns + ) do + assigns = + assigns + |> assign_date(current_path, params, time_zone) + + assigns = + case mode do + "week" -> + assigns + |> assigns_week(current_path, time_zone, params) + + "month" -> + assigns + |> assigns_month(current_path, time_zone, params) + + _ -> + assigns + |> assigns_month(current_path, time_zone, params) + end + + ~H""" +
+
+
+
+ + <%= if @mode == "month" do %> + + <% else %> + <%= case date_to_month(@beginning_of_week) == date_to_month(@end_of_week) do %> + <% true -> %> + + <% _ -> %> + <%= if date_to_year(@beginning_of_week) == date_to_year(@end_of_week) do %> + + <% else %> + + <% end %> + <% end %> + <% end %> + +
+
+ <%= live_patch to: "#{if @mode == "month" do @previous_month_path else @previous_week_path end}" do %> + + <% end %> + <%= live_patch to: "#{if @mode == "month" do @present_month_path else @present_week_path end}" do %> + + <% end %> + <%= live_patch to: "#{if @mode == "month" do @next_month_path else @next_week_path end}" do %> + + <% end %> +
+ +
+ + +
+
+ <%= live_patch to: "#{if @mode == "month" do @present_month_path else @present_week_path end}" do %> + + <% end %> +
+ <%= live_patch to: @present_week_path do %> + + <% end %> + <%= live_patch to: @present_month_path do %> + + <% end %> +
+
+
+
+
+
+
+ <%= if @mode == "month" do %> + <.calendar_month id="calendar_month" current_organization={@current_organization} current_path={@current_path} params={@params} sessions={@sessions} beginning_of_month={@beginning_of_month} end_of_month={@end_of_month} time_zone={@time_zone} /> + <% else %> + <.calendar_week id="calendar_week" current_organization={@current_organization} current_path={@current_path} current={@current} params={@params} sessions={@sessions} beginning_of_week={@beginning_of_week} end_of_week={@end_of_week} time_zone={@time_zone} /> + <% end %> +
+ """ + end + + defp assign_date(assigns, current_path, params, time_zone) do + current = current_from_params(time_zone, params) + + current_year = + current + |> date_to_year() + + current_month = + current + |> date_to_month() + + current_day = + current + |> date_to_day() + + present_year = + Timex.today(time_zone) + |> date_to_year() + + present_month = + Timex.today(time_zone) + |> date_to_month() + + present_day = + Timex.today(time_zone) + |> date_to_day() + + present_week_path = + build_path(current_path, %{ + mode: "week", + day: present_day, + month: present_month, + year: present_year + }) + + current_week_path = + build_path(current_path, %{ + mode: "week", + day: current_day, + month: current_month, + year: current_year + }) + + present_month_path = + build_path(current_path, %{ + mode: "month", + day: present_day, + month: present_month, + year: present_year + }) + + current_month_path = + build_path(current_path, %{ + mode: "month", + day: current_day, + month: current_month, + year: current_year + }) + + assigns + |> assign(present_month_path: present_month_path) + |> assign(present_week_path: present_week_path) + |> assign(current_month_path: current_month_path) + |> assign(current_week_path: current_week_path) + |> assign(current: current) + end + + defp assigns_week(assigns, current_path, time_zone, params) do + current = current_from_params(time_zone, params) + beginning_of_week = Timex.beginning_of_week(current) + end_of_week = Timex.end_of_week(current) + + previous_week_date = + current + |> Timex.add(Duration.from_days(-7)) + + next_week_date = + current + |> Timex.add(Duration.from_days(7)) + + previous_week_day = + previous_week_date + |> date_to_day() + + previous_week_month = + previous_week_date + |> date_to_month() + + previous_week_year = + previous_week_date + |> date_to_year() + + next_week_day = + next_week_date + |> date_to_day() + + next_week_month = + next_week_date + |> date_to_month() + + next_week_year = + next_week_date + |> date_to_year() + + previous_week_path = + build_path(current_path, %{ + mode: "week", + day: previous_week_day, + month: previous_week_month, + year: previous_week_year + }) + + next_week_path = + build_path(current_path, %{ + mode: "week", + day: next_week_day, + month: next_week_month, + year: next_week_year + }) + + assigns + |> assign(beginning_of_week: beginning_of_week) + |> assign(end_of_week: end_of_week) + |> assign(previous_week_path: previous_week_path) + |> assign(next_week_path: next_week_path) + end + + defp assigns_month(assigns, current_path, time_zone, params) do + current = current_from_params(time_zone, params) + beginning_of_month = Timex.beginning_of_month(current) + end_of_month = Timex.end_of_month(current) + + last_day_previous_month = + beginning_of_month + |> Timex.add(Duration.from_days(-1)) + + first_day_next_month = + end_of_month + |> Timex.add(Duration.from_days(1)) + + previous_month = + last_day_previous_month + |> date_to_month() + + next_month = + first_day_next_month + |> date_to_month() + + previous_month_year = + last_day_previous_month + |> date_to_year() + + next_month_year = + first_day_next_month + |> date_to_year() + + previous_month_path = + build_path(current_path, %{mode: "month", month: previous_month, year: previous_month_year}) + + next_month_path = + build_path(current_path, %{mode: "month", month: next_month, year: next_month_year}) + + assigns + |> assign(beginning_of_month: beginning_of_month) + |> assign(end_of_month: end_of_month) + |> assign(previous_month_path: previous_month_path) + |> assign(next_month_path: next_month_path) + end +end diff --git a/lib/atomic_web/components/calendar/month.ex b/lib/atomic_web/components/calendar/month.ex new file mode 100644 index 000000000..95f7e17f7 --- /dev/null +++ b/lib/atomic_web/components/calendar/month.ex @@ -0,0 +1,181 @@ +defmodule AtomicWeb.Components.CalendarMonth do + @moduledoc false + use AtomicWeb, :component + + import AtomicWeb.CalendarUtils + import AtomicWeb.Components.Badges + + def calendar_month(assigns) do + organization = assigns.current_organization + + ~H""" +
+
+
+ Mon +
+
+ Tue +
+
+ Wed +
+
+ Thu +
+
+ Fri +
+
+ Sat +
+
+ Sun +
+
+
+
+ <%= for i <- 0..@end_of_month.day - 1 do %> + <.day index={i} current_organization={@current_organization} params={@params} current_path={@current_path} sessions={@sessions} date={Timex.shift(@beginning_of_month, days: i)} time_zone={@time_zone} /> + <% end %> +
+
+
+
+
    + <%= for session <- get_date_sessions(@sessions, current_from_params(@time_zone, @params)) do %> + <%= if session.activity do %> + <%= live_patch to: Routes.activity_show_path(AtomicWeb.Endpoint, :show, session.activity, organization) do %> +
  1. +
    +

    + <%= session.activity.title %> + <%!-- <%= if Gettext.get_locale() == "en" do + if session.activity.title_en do + session.activity.title_en + else + "" + end + else + if session.activity.title_pt do + session.activity.title_pt + else + "" + end + end %> --%> +

    +
    + <.badge_dot url={Routes.activity_index_path(AtomicWeb.Endpoint, :index, organization)} color="purple"> + Activity + + +
    +
    +
  2. + <% end %> + <% end %> + <% end %> +
+
+ """ + end + + defp day( + %{index: index, date: date, time_zone: time_zone, current_organization: organization} = + assigns + ) do + weekday = Timex.weekday(date, :monday) + today? = Timex.compare(date, Timex.today(time_zone)) + + class = + class_list([ + {"relative py-2 px-3 lg:min-h-[110px] lg:flex hidden", true}, + {col_start(weekday), index == 0}, + {"bg-white", today? >= 0}, + {"bg-zinc-50 text-zinc-500", today? < 0} + ]) + + assigns = + assigns + |> assign(disabled: today? < 0) + |> assign(:text, Timex.format!(date, "{D}")) + |> assign(:class, class) + |> assign(:date, date) + |> assign(:today?, today?) + + ~H""" +
+ +
    + <%= for session <- get_date_sessions(@sessions, @date) do %> +
  1. + <%= if session.activity do %> + <%= live_patch to: Routes.activity_show_path(AtomicWeb.Endpoint, :show, session.activity, organization), class: "group flex" do %> +

    + <%= session.activity.title %> + <%!-- <%= if Gettext.get_locale() == "en" do + if session.activity.title_en do + session.activity.title_en + else + "" + end + else + if session.activity.title_pt do + session.activity.title_pt + else + "" + end + end %> --%> +

    + + <% end %> + <% end %> +
  2. + <% end %> +
+
+ <%= live_patch to: build_path(@current_path, %{mode: "month", day: date_to_day(@date), month: date_to_month(@date), year: date_to_year(@date)}), class: "#{if @index == 0 do col_start(weekday) end} min-h-[56px] flex w-full flex-col bg-white px-3 py-2 text-zinc-900 hover:bg-zinc-100 focus:z-10 lg:hidden" do %> + + <%= if (sessions = get_date_sessions(@sessions, @date)) != [] do %> + Enum.count(sessions) events + + <%= for session <- sessions do %> + <%= if session.activity do %> + + <% end %> + <% end %> + + <% end %> + <% end %> + """ + end +end diff --git a/lib/atomic_web/components/calendar/week.ex b/lib/atomic_web/components/calendar/week.ex new file mode 100644 index 000000000..24add0e11 --- /dev/null +++ b/lib/atomic_web/components/calendar/week.ex @@ -0,0 +1,225 @@ +defmodule AtomicWeb.Components.CalendarWeek do + @moduledoc false + use AtomicWeb, :component + + alias Timex.Duration + + import AtomicWeb.CalendarUtils + + def calendar_week( + %{ + beginning_of_week: beginning_of_week, + time_zone: time_zone, + current_organization: organization + } = assigns + ) do + today = Timex.today(time_zone) + + assigns = + assigns + |> assign(week_mobile: ["M", "T", "W", "T", "F", "S", "S"]) + |> assign(week: ["Mon ", "Tue ", "Wed ", "Thu ", "Fri ", "Sat ", "Sun "]) + |> assign(beginning_of_week: beginning_of_week) + |> assign(today: today) + + ~H""" +
+
+
+
+ <%= for idx <- 0..6 do %> + <% day_of_week = beginning_of_week |> Timex.add(Duration.from_days(idx)) %> + <%= live_patch to: build_path(@current_path, %{"mode" => "week", "day" => day_of_week |> date_to_day(), "month" => @params["month"], "year" => @params["year"]}), class: "flex flex-col items-center py-2" do %> + <%= Enum.at(@week_mobile, idx) %> + date_to_day() == @params["day"] do + "bg-zinc-900 rounded-full text-white" + else + "text-zinc-900" + end + end} flex items-center justify-center w-8 h-8 mt-1 font-semibold" + }> + <%= day_of_week |> date_to_day() %> + + <% end %> + <% end %> +
+ +
+
+
+
+ +
+
+
+
8H
+
+
+
+
9H
+
+
+
+
10H
+
+
+
+
11H
+
+
+
+
12H
+
+
+
+
13H
+
+
+
+
14H
+
+
+
+
15H
+
+
+
+
16H
+
+
+
+
17H
+
+
+
+
18H
+
+
+
+
19H
+
+
+
+
20H
+
+
+
+
21H
+
+
+
+
22H
+
+
+
+ + + +
    + <.day date={@current} idx={0} sessions={@sessions} organization={organization} /> +
+ +
+
+
+
+ """ + end + + defp day(assigns) do + ~H""" + <%= for session <- get_date_sessions(@sessions, @date) do %> + <%= if session.activity do %> +
  • + <%= live_patch to: Routes.activity_show_path(AtomicWeb.Endpoint, :show, session.activity, assigns.organization) do %> +
    +

    + session.activity.title + <%!-- <%= if Gettext.get_locale() == "en" do + if session.activity.title_en do + session.activity.title_en + else + "" + end + else + if session.activity.title_pt do + session.activity.title_pt + else + "" + end + end %> --%> +

    +

    + +

    +
    + <% end %> +
  • + <% end %> + <% end %> + """ + end + + defp calc_row_start(start) do + hours = + start + |> Timex.format!("{h24}") + |> String.to_integer() + + minutes = + start + |> Timex.format!("{m}") + |> String.to_integer() + + minutes = (minutes * 20 / 60) |> trunc() + + 2 + (hours - 8) * 20 + minutes + end + + defp calc_time(start, finish) do + time_diff = (NaiveDateTime.diff(finish, start) / 3600) |> trunc() + + 2 + 20 * time_diff + end +end diff --git a/lib/atomic_web/config.ex b/lib/atomic_web/config.ex index da7ab9824..c4960522c 100644 --- a/lib/atomic_web/config.ex +++ b/lib/atomic_web/config.ex @@ -17,7 +17,7 @@ defmodule AtomicWeb.Config do key: :calendar, title: "Calendar", icon: :calendar, - url: "", + url: Routes.calendar_show_path(conn, :show), tabs: [] }, %{ diff --git a/lib/atomic_web/live/activity_live/index.ex b/lib/atomic_web/live/activity_live/index.ex index 19099c586..0a89f50fc 100644 --- a/lib/atomic_web/live/activity_live/index.ex +++ b/lib/atomic_web/live/activity_live/index.ex @@ -4,6 +4,7 @@ defmodule AtomicWeb.ActivityLive.Index do alias Atomic.Accounts alias Atomic.Activities alias Atomic.Activities.Activity + alias Atomic.Organizations @impl true def mount(params, _session, socket) do @@ -38,9 +39,11 @@ defmodule AtomicWeb.ActivityLive.Index do |> assign(:activity, %Activity{}) end - defp apply_action(socket, :index, _params) do + defp apply_action(socket, :index, params) do + organization = Organizations.get_organization!(params["organization_id"]) + socket - |> assign(:page_title, "Listing Activities") + |> assign(:page_title, "#{organization.name} Activities") |> assign(:activity, nil) end diff --git a/lib/atomic_web/live/activity_live/index.html.heex b/lib/atomic_web/live/activity_live/index.html.heex index eeb1f7480..a1d83e8c3 100644 --- a/lib/atomic_web/live/activity_live/index.html.heex +++ b/lib/atomic_web/live/activity_live/index.html.heex @@ -1,32 +1,30 @@
    -
    -
    -
    -
    -
    -

    - <%= gettext("Activities") %> -

    -
    +
    +
    +
    +
    +

    + <%= gettext("Activities") %> +

    -
    -
    -
    +
    +
    + - +
    diff --git a/lib/atomic_web/live/board_live/edit.ex b/lib/atomic_web/live/board_live/edit.ex index dfe1c3282..6ff188d6f 100644 --- a/lib/atomic_web/live/board_live/edit.ex +++ b/lib/atomic_web/live/board_live/edit.ex @@ -13,11 +13,12 @@ defmodule AtomicWeb.BoardLive.Edit do def handle_params(%{"organization_id" => organization_id, "id" => id}, _url, socket) do user_organization = Organizations.get_user_organization!(id, [:user, :organization]) users = Enum.map(Accounts.list_users(), fn u -> [key: u.email, value: u.id] end) + organization = user_organization.organization if user_organization.organization_id == organization_id do {:noreply, socket - |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:page_title, page_title(socket.assigns.live_action, organization)) |> assign(:user_organization, user_organization) |> assign(:users, users) |> assign(:current_user, socket.assigns.current_user)} @@ -26,7 +27,7 @@ defmodule AtomicWeb.BoardLive.Edit do end end - defp page_title(:index), do: "List board" - defp page_title(:show), do: "Show board" - defp page_title(:edit), do: "Edit board" + defp page_title(:index, organization), do: "#{organization.name} board" + defp page_title(:show, organization), do: "#{organization.name} board" + defp page_title(:edit, _), do: "Edit board" end diff --git a/lib/atomic_web/live/board_live/index.ex b/lib/atomic_web/live/board_live/index.ex index 665e773e1..2be01e386 100644 --- a/lib/atomic_web/live/board_live/index.ex +++ b/lib/atomic_web/live/board_live/index.ex @@ -11,6 +11,7 @@ defmodule AtomicWeb.BoardLive.Index do @impl true def handle_params(%{"organization_id" => id}, _, socket) do users_organizations = list_users_organizations(id) + organization = Organizations.get_organization!(id) entries = [ %{ @@ -23,7 +24,7 @@ defmodule AtomicWeb.BoardLive.Index do socket |> assign(:current_page, :board) |> assign(:breadcrumb_entries, entries) - |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:page_title, page_title(socket.assigns.live_action, organization)) |> assign(:users_organizations, users_organizations) |> assign(:id, id)} end @@ -31,6 +32,7 @@ defmodule AtomicWeb.BoardLive.Index do @impl true def handle_event("delete", %{"id" => id}, socket) do user_organization = Organizations.get_user_organization!(id) + {:ok, user_org} = Organizations.delete_user_organization(user_organization) {:noreply, @@ -41,7 +43,7 @@ defmodule AtomicWeb.BoardLive.Index do Organizations.list_users_organizations(where: [organization_id: id]) end - defp page_title(:index), do: "List board" - defp page_title(:show), do: "Show board" - defp page_title(:edit), do: "Edit board" + defp page_title(:index, organization), do: "#{organization.name} Board" + defp page_title(:show, organization), do: "#{organization.name} Board" + defp page_title(:edit, _organization), do: "Edit board" end diff --git a/lib/atomic_web/live/board_live/index.html.heex b/lib/atomic_web/live/board_live/index.html.heex index d11343778..8e8b9179f 100644 --- a/lib/atomic_web/live/board_live/index.html.heex +++ b/lib/atomic_web/live/board_live/index.html.heex @@ -1,24 +1,125 @@ -
    -

    Board

    - <%= live_redirect("New", to: Routes.board_new_path(@socket, :new, @id)) %> - - - - - - - - <%= for user_organization <- @users_organizations do %> - - - - - - - <% end %> -
    NameTitleYearActions
    Nome<%= user_organization.title %><%= user_organization.year %> - <%= live_redirect("Show", to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization)) %> - <%= live_redirect("Edit", to: Routes.board_edit_path(@socket, :edit, user_organization.organization_id, user_organization)) %> - <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: user_organization.id, data: [confirm: "Are you sure?"]) %> -
    +
    +
    +
    +
    +

    + <%= gettext("Board") %> +

    +
    + +
    +

    Presidência

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Vogais

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Departamento de Centro de Apoio ao Open Source

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Departamento de Marketing e Conteúdo

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Departamento de Merch e Parcerias

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Departamento Recreativo

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Departamento Pedagógico

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Mesa da Assembleia Geral

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +

    Conselho Fiscal

    +
      + <%= for user_organization <- Enum.take(@users_organizations,5) do %> + <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> +
    • + +

      Michael Foster

      +

      Co-Founder / CTO

      +
    • + <% end %> + <% end %> +
    +
    diff --git a/lib/atomic_web/live/calendar_live/show.ex b/lib/atomic_web/live/calendar_live/show.ex new file mode 100644 index 000000000..31351a985 --- /dev/null +++ b/lib/atomic_web/live/calendar_live/show.ex @@ -0,0 +1,58 @@ +defmodule AtomicWeb.CalendarLive.Show do + @moduledoc false + use AtomicWeb, :live_view + + alias Atomic.Activities + + import AtomicWeb.CalendarUtils + import AtomicWeb.Components.Calendar + + @impl true + def mount(_params, _session, socket) do + {:ok, socket} + end + + @impl true + def handle_params(params, _uri, socket) do + mode = params["mode"] || "month" + + entries = [ + %{ + name: gettext("Calendar"), + route: Routes.calendar_show_path(socket, :show) + } + ] + + {:noreply, + socket + |> assign(:current_page, :calendar) + |> assign(:breadcrumb_entries, entries) + |> assign(:params, params) + |> assign(:mode, mode) + |> assign( + list_activities(socket.assigns.time_zone, mode, params, socket.assigns.current_user) + )} + end + + defp list_activities(time_zone, mode, params, _user) do + current = current_from_params(time_zone, params) + + start = + if mode == "month" do + Timex.beginning_of_month(current) |> Timex.to_naive_datetime() + else + Timex.beginning_of_week(current) |> Timex.to_naive_datetime() + end + + finish = + if mode == "month" do + Timex.end_of_month(current) |> Timex.to_naive_datetime() + else + Timex.end_of_week(current) |> Timex.to_naive_datetime() + end + + %{ + sessions: Activities.list_sessions_from_to(start, finish, preloads: [:activity]) + } + end +end diff --git a/lib/atomic_web/live/calendar_live/show.html.heex b/lib/atomic_web/live/calendar_live/show.html.heex new file mode 100644 index 000000000..30ba46688 --- /dev/null +++ b/lib/atomic_web/live/calendar_live/show.html.heex @@ -0,0 +1,8 @@ +
    +
    +

    + <%= gettext("Calendar") %> +

    +
    + <.calendar id="calendar" current_path={Routes.calendar_show_path(@socket, :show)} sessions={@sessions} params={@params} mode={@mode} time_zone={@time_zone} current_organization={@current_organization} /> +
    diff --git a/lib/atomic_web/live/department_live/index.ex b/lib/atomic_web/live/department_live/index.ex index f4621e5b8..19589b335 100644 --- a/lib/atomic_web/live/department_live/index.ex +++ b/lib/atomic_web/live/department_live/index.ex @@ -3,6 +3,7 @@ defmodule AtomicWeb.DepartmentLive.Index do alias Atomic.Departments alias Atomic.Departments.Department + alias Atomic.Organizations @impl true def mount(%{"organization_id" => organization_id}, _session, socket) do @@ -43,9 +44,11 @@ defmodule AtomicWeb.DepartmentLive.Index do |> assign(:department, %Department{}) end - defp apply_action(socket, :index, _params) do + defp apply_action(socket, :index, params) do + organization = Organizations.get_organization!(params["organization_id"]) + socket - |> assign(:page_title, "Listing Departments") + |> assign(:page_title, "#{organization.name} Departments") |> assign(:department, nil) end diff --git a/lib/atomic_web/live/department_live/index.html.heex b/lib/atomic_web/live/department_live/index.html.heex index 580ac0fe4..eab2127f6 100644 --- a/lib/atomic_web/live/department_live/index.html.heex +++ b/lib/atomic_web/live/department_live/index.html.heex @@ -1,32 +1,67 @@ -

    Listing Departments

    - <%= if @live_action in [:new, :edit] do %> <.modal return_to={Routes.department_index_path(@socket, :index, @current_organization)}> <.live_component module={AtomicWeb.DepartmentLive.FormComponent} id={@department.id || :new} organization={@current_organization} title={@page_title} action={@live_action} department={@department} return_to={Routes.department_index_path(@socket, :index, @current_organization)} /> <% end %> +
    +
    +
    +
    +

    + <%= gettext("Departments") %> +

    +
    + +
    +
    +
    + +
    +
    +
    +
    - - - - - - - - - - <%= for department <- @departments do %> - - - - - - <% end %> - -
    Name
    <%= department.name %> - <%= live_redirect("Show", to: Routes.department_show_path(@socket, :show, @current_organization, department)) %> - <%= live_patch("Edit", to: Routes.department_index_path(@socket, :edit, @current_organization, department)) %> - <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: department.id, data: [confirm: "Are you sure?"]) %> -
    - -<%= live_patch("New Department", to: Routes.department_index_path(@socket, :new, @current_organization)) %> +
      + <%= for department <- @departments do %> +
    • + <%= live_patch to: Routes.department_show_path(@socket, :show, @current_organization, department), class: "" do %> +
      + + + <%= Atomic.Accounts.extract_initials(department.name) %> + + +

      <%= department.name %>

      +
      +
      +
      +
      + <%= live_patch("Edit", + to: Routes.department_index_path(@socket, :edit, @current_organization, department), + class: "relative -mr-px inline-flex w-0 flex-1 items-center justify-center gap-x-3 rounded-bl-lg border border-transparent py-4 text-sm font-semibold text-gray-900 hover:bg-gray-300" + ) %> +
      +
      + <%= link("Delete", + to: "#", + phx_click: "delete", + class: "relative -mr-px inline-flex w-0 flex-1 items-center justify-center gap-x-3 rounded-bl-lg border border-transparent py-4 text-sm font-semibold text-gray-900 hover:bg-gray-300", + phx_value_id: department.id, + data: [confirm: "Are you sure?"] + ) %> +
      +
      +
      + <% end %> +
    • + <% end %> +
    diff --git a/lib/atomic_web/live/home_live/index.ex b/lib/atomic_web/live/home_live/index.ex index f282e43b5..809d32918 100644 --- a/lib/atomic_web/live/home_live/index.ex +++ b/lib/atomic_web/live/home_live/index.ex @@ -2,13 +2,29 @@ defmodule AtomicWeb.HomeLive.Index do @moduledoc false use AtomicWeb, :live_view + alias Atomic.Activities + alias Atomic.Partnerships + alias Atomic.Uploaders.Card + + import AtomicWeb.CalendarUtils + import AtomicWeb.Components.Calendar + import AtomicWeb.ViewUtils + @impl true def mount(_params, _session, socket) do {:ok, socket} end @impl true - def handle_params(_params, _url, socket) do + def handle_params(params, _url, socket) do + partners = + Partnerships.list_partnerships_by_organization_id(socket.assigns.current_organization.id) + + actitivities = + Activities.list_activities_by_organization_id(socket.assigns.current_organization.id, []) + + mode = params["mode"] || "month" + entries = [ %{ name: gettext("Home"), @@ -20,6 +36,36 @@ defmodule AtomicWeb.HomeLive.Index do socket |> assign(:page_title, "Home") |> assign(:breadcrumb_entries, entries) - |> assign(:current_page, :home)} + |> assign(:current_page, :home) + |> assign(:partners, partners) + |> assign(:activities, actitivities) + |> assign(:time_zone, socket.assigns.time_zone) + |> assign(:params, params) + |> assign(:mode, mode) + |> assign( + list_activities(socket.assigns.time_zone, mode, params, socket.assigns.current_user) + )} + end + + defp list_activities(time_zone, mode, params, _user) do + current = current_from_params(time_zone, params) + + start = + if mode == "month" do + Timex.beginning_of_month(current) |> Timex.to_naive_datetime() + else + Timex.beginning_of_week(current) |> Timex.to_naive_datetime() + end + + finish = + if mode == "month" do + Timex.end_of_month(current) |> Timex.to_naive_datetime() + else + Timex.end_of_week(current) |> Timex.to_naive_datetime() + end + + %{ + sessions: Activities.list_sessions_from_to(start, finish, preloads: [:activity]) + } end end diff --git a/lib/atomic_web/live/home_live/index.html.heex b/lib/atomic_web/live/home_live/index.html.heex index 719de6cd4..53f656366 100644 --- a/lib/atomic_web/live/home_live/index.html.heex +++ b/lib/atomic_web/live/home_live/index.html.heex @@ -1,3 +1,117 @@ -
    -

    Atomic Home

    +
    +
    + +
    +

    + <%= @current_organization.name %> +

    + + <%= @current_organization.description %> + +
    +
    + +
    +
    +
    +

    + <%= gettext("News") %> +

    + + <%= live_patch("See all", to: Routes.partner_index_path(@socket, :index, @current_organization), class: "text-sm font-semibold text-gray-300 ml-4") %> +
    +
      + <%= for new <- Enum.take(@activities,5) do %> +
    • +
      +
      +

      + + <%= new.title %> +

      + + <%= display_date(new.inserted_at) %> <%= display_time(new.inserted_at) %> + + + <%= new.description %> + +
      +
      +
    • + <% end %> +
    +
    + +
    +
    +

    + <%= gettext("Partners") %> +

    + + <%= live_patch("See all", to: Routes.partner_index_path(@socket, :index, @current_organization), class: "text-sm font-semibold text-gray-300 ml-4") %> +
    +
      + <%= for partner <- Enum.take(@partners,5) do %> +
    • +
      +
      +
      +
      + <%= if partner.image do %> + + <% else %> + + + <%= Atomic.Accounts.extract_initials(partner.name) %> + + + <% end %> +
      +

      + <%= partner.name %> +

      +
      + + <%= partner.description %> + +
      +
      +
    • + <% end %> +
    +
    + +
    +
    +
    +

    <%= gettext("Activities") %>

    + + <%= live_patch("See all", to: Routes.activity_index_path(@socket, :index, @current_organization), class: "text-sm font-semibold text-gray-300 ml-4") %> +
    + <.calendar id="calendar" current_path={Routes.calendar_show_path(@socket, :show)} sessions={@sessions} params={@params} mode={@mode} time_zone={@time_zone} current_organization={@current_organization} /> +
    +
    +
      + <%= for activity <- Enum.take(@activities,5) do %> +
    • +
      +
      +

      + + <%= activity.title %> +

      + + <%= display_date(activity.inserted_at) %> <%= display_time(activity.inserted_at) %> + + + <%= activity.description %> + +
      +
      +
    • + <% end %> +
    +
    +
    +
    diff --git a/lib/atomic_web/live/hooks.ex b/lib/atomic_web/live/hooks.ex index 5a7548ec4..08766c48b 100644 --- a/lib/atomic_web/live/hooks.ex +++ b/lib/atomic_web/live/hooks.ex @@ -18,6 +18,8 @@ defmodule AtomicWeb.Hooks do socket ) do current_user = Accounts.get_user_by_session_token(user_token) + owner = Application.get_env(:atomic, :owner) + time_zone = get_connect_params(socket)["timezone"] || owner.time_zone if current_user.default_organization_id != nil do socket = @@ -27,12 +29,14 @@ defmodule AtomicWeb.Hooks do :current_organization, Organizations.get_organization!(current_user.default_organization_id) ) + |> assign(:time_zone, time_zone) {:cont, socket} else socket = socket |> assign(:current_user, current_user) + |> assign(:time_zone, time_zone) {:cont, socket} end @@ -41,6 +45,8 @@ defmodule AtomicWeb.Hooks do def on_mount(:general_user_state, _params, session, socket) do current_organization = session["current_organization"] current_user = session["user_token"] + owner = Application.get_env(:atomic, :owner) + time_zone = get_connect_params(socket)["timezone"] || owner.time_zone case {current_organization, current_user} do {nil, nil} -> @@ -55,7 +61,8 @@ defmodule AtomicWeb.Hooks do |> assign( :current_organization, Organizations.get_organization!(user.default_organization_id) - )} + ) + |> assign(:time_zone, time_zone)} {_, nil} -> {:cont, @@ -71,7 +78,8 @@ defmodule AtomicWeb.Hooks do |> assign( :current_organization, Organizations.get_organization!(user.default_organization_id) - )} + ) + |> assign(:time_zone, time_zone)} end end diff --git a/lib/atomic_web/live/membership_live/edit.ex b/lib/atomic_web/live/membership_live/edit.ex index bb418b48e..d8d4c2792 100644 --- a/lib/atomic_web/live/membership_live/edit.ex +++ b/lib/atomic_web/live/membership_live/edit.ex @@ -11,11 +11,12 @@ defmodule AtomicWeb.MembershipLive.Edit do @impl true def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do membership = Organizations.get_membership!(id, [:user, :organization, :created_by]) + organization = Organizations.get_organization!(organization_id) if membership.organization_id == organization_id do {:noreply, socket - |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:page_title, page_title(socket.assigns.live_action, organization)) |> assign(:organization, organization_id) |> assign(:membership, membership) |> assign(:current_user, socket.assigns.current_user) @@ -28,7 +29,7 @@ defmodule AtomicWeb.MembershipLive.Edit do end end - defp page_title(:index), do: "List memberships" - defp page_title(:show), do: "Show membership" - defp page_title(:edit), do: "Edit membership" + defp page_title(:index, organization), do: "#{organization.name} Memberships" + defp page_title(:show, organization), do: "#{organization.name} Membership" + defp page_title(:edit, _organization), do: "Edit Membership" end diff --git a/lib/atomic_web/live/membership_live/index.ex b/lib/atomic_web/live/membership_live/index.ex index a7d9a79b5..dc5aba93e 100644 --- a/lib/atomic_web/live/membership_live/index.ex +++ b/lib/atomic_web/live/membership_live/index.ex @@ -2,7 +2,7 @@ defmodule AtomicWeb.MembershipLive.Index do use AtomicWeb, :live_view alias Atomic.Organizations - + import AtomicWeb.ViewUtils @impl true def mount(_params, _session, socket) do {:ok, socket} @@ -11,9 +11,11 @@ defmodule AtomicWeb.MembershipLive.Index do @impl true def handle_params(%{"organization_id" => id}, _, socket) do memberships = - Organizations.list_memberships(%{"organization_id" => id}, [:user]) + Organizations.list_memberships(%{"organization_id" => id}, [:user, :created_by]) |> Enum.filter(fn m -> m.role != :follower end) + organization = Organizations.get_organization!(id) + entries = [ %{ name: gettext("Memberships"), @@ -25,11 +27,11 @@ defmodule AtomicWeb.MembershipLive.Index do socket |> assign(:current_page, :memberships) |> assign(:breadcrumb_entries, entries) - |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:page_title, page_title(socket.assigns.live_action, organization)) |> assign(:memberships, memberships)} end - defp page_title(:index), do: "List memberships" - defp page_title(:show), do: "Show membership" - defp page_title(:edit), do: "Edit membership" + defp page_title(:index, organization), do: "#{organization.name} Memberships" + defp page_title(:show, organization), do: "#{organization.name} Membership" + defp page_title(:edit, _), do: "Edit membership" end diff --git a/lib/atomic_web/live/membership_live/index.html.heex b/lib/atomic_web/live/membership_live/index.html.heex index 5445ef1b3..0f5e7b08f 100644 --- a/lib/atomic_web/live/membership_live/index.html.heex +++ b/lib/atomic_web/live/membership_live/index.html.heex @@ -1,38 +1,52 @@ -
    -

    Memberships

    +
    +
    +
    +

    + <%= gettext("Memberships") %> +

    +
    +
    - - - - - - - - - - - - <%= for membership <- @memberships do %> - - - - - - - - - - - <% end %> -
    NumberNameEmailPhone NumberRequested AtAccepted ByAccepted AtActions
    <%= membership.number %>John Doe<%= membership.user.email %>912345678<%= membership.inserted_at %>Jane Doe<%= membership.updated_at %> - <%= link to: Routes.membership_show_path(@socket, :show, membership.organization_id, membership) do %> - Show - <% end %> - <%= link to: Routes.membership_edit_path(@socket, :edit, membership.organization_id, membership) do %> - Edit - <% end %> - <%= link to: Routes.membership_edit_path(@socket, :edit, membership.organization_id, membership) do %> - Delete - <% end %> -
    +
    +
    +
    +
    + + + + + + + + + + + + + + + <%= for membership <- @memberships do %> + + + + + + + + + + + <% end %> + +
    NumberNameEmailPhone NumberRequested AtUpdated ByUpdated At + Edit +
    <%= membership.number %><%= membership.user.name %><%= membership.user.email %>Phone number<%= display_date(membership.inserted_at) %> <%= display_time(membership.inserted_at) %><%= membership.created_by.name %><%= display_date(membership.updated_at) %> <%= display_time(membership.updated_at) %> + <%= link to: Routes.membership_edit_path(@socket, :edit, membership.organization_id, membership), class: "text-indigo-600 hover:text-indigo-900" do %> + Edit + <% end %> +
    +
    +
    +
    +
    diff --git a/lib/atomic_web/live/membership_live/show.ex b/lib/atomic_web/live/membership_live/show.ex index 9b2e1c42b..a11842746 100644 --- a/lib/atomic_web/live/membership_live/show.ex +++ b/lib/atomic_web/live/membership_live/show.ex @@ -11,18 +11,19 @@ defmodule AtomicWeb.MembershipLive.Show do @impl true def handle_params(%{"organization_id" => organization_id, "id" => id}, _, socket) do membership = Organizations.get_membership!(id, [:user, :organization, :created_by]) + organization = Organizations.get_organization!(organization_id) if membership.organization_id == organization_id do {:noreply, socket - |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:page_title, page_title(socket.assigns.live_action, organization)) |> assign(:membership, membership)} else raise AtomicWeb.MismatchError end end - defp page_title(:index), do: "List memberships" - defp page_title(:show), do: "Show membership" - defp page_title(:edit), do: "Edit membership" + defp page_title(:index, organization), do: "#{organization.name} Memberships" + defp page_title(:show, organization), do: "#{organization.name} Membership" + defp page_title(:edit, _), do: "Edit membership" end diff --git a/lib/atomic_web/live/organization_live/index.ex b/lib/atomic_web/live/organization_live/index.ex index 09278a785..c05e8e2df 100644 --- a/lib/atomic_web/live/organization_live/index.ex +++ b/lib/atomic_web/live/organization_live/index.ex @@ -51,7 +51,7 @@ defmodule AtomicWeb.OrganizationLive.Index do defp apply_action(socket, :index, _params) do socket - |> assign(:page_title, "Listing Organizations") + |> assign(:page_title, "Organizations") |> assign(:organization, nil) end diff --git a/lib/atomic_web/live/organization_live/index.html.heex b/lib/atomic_web/live/organization_live/index.html.heex index 1c8bd7711..8a22ba3cb 100644 --- a/lib/atomic_web/live/organization_live/index.html.heex +++ b/lib/atomic_web/live/organization_live/index.html.heex @@ -1,5 +1,3 @@ -

    Listing Organizations

    - <%= if @live_action in [:new, :edit] do %> <.modal return_to={Routes.organization_index_path(@socket, :index)}> <.live_component module={AtomicWeb.OrganizationLive.FormComponent} id={@organization || :new} title={@page_title} action={@live_action} organization={@organization} return_to={Routes.organization_index_path(@socket, :index)} /> diff --git a/lib/atomic_web/live/partner_live/index.ex b/lib/atomic_web/live/partner_live/index.ex index 9a4a72fd9..4d47c1aff 100644 --- a/lib/atomic_web/live/partner_live/index.ex +++ b/lib/atomic_web/live/partner_live/index.ex @@ -1,6 +1,7 @@ defmodule AtomicWeb.PartnerLive.Index do use AtomicWeb, :live_view + alias Atomic.Organizations alias Atomic.Partnerships alias Atomic.Partnerships.Partner @@ -13,7 +14,7 @@ defmodule AtomicWeb.PartnerLive.Index do def handle_params(params, _url, socket) do entries = [ %{ - name: gettext("Partnerships"), + name: gettext("Partners"), route: Routes.partner_index_path(socket, :index, params["organization_id"]) } ] @@ -43,9 +44,11 @@ defmodule AtomicWeb.PartnerLive.Index do |> assign(:partner, %Partner{}) end - defp apply_action(socket, :index, _params) do + defp apply_action(socket, :index, params) do + organization = Organizations.get_organization!(params["organization_id"]) + socket - |> assign(:page_title, "Listing Partnerships") + |> assign(:page_title, "#{organization.name} Partners") |> assign(:partner, nil) end diff --git a/lib/atomic_web/live/partner_live/index.html.heex b/lib/atomic_web/live/partner_live/index.html.heex index 7f892ded7..4b2f0bbec 100644 --- a/lib/atomic_web/live/partner_live/index.html.heex +++ b/lib/atomic_web/live/partner_live/index.html.heex @@ -1,34 +1,61 @@ -

    Listing Partnerships

    - <%= if @live_action in [:new, :edit] do %> <.modal return_to={Routes.partner_index_path(@socket, :index, @current_organization)}> <.live_component module={AtomicWeb.PartnerLive.FormComponent} id={@partner.id || :new} organization={@current_organization} title={@page_title} action={@live_action} partner={@partner} return_to={Routes.partner_index_path(@socket, :index, @current_organization)} /> <% end %> - - - - - - - - - - - <%= for partner <- @partnerships do %> - - - - - - - <% end %> - -
    NameDescription
    <%= partner.name %><%= partner.description %> - <%= live_redirect("Show", to: Routes.partner_show_path(@socket, :show, @current_organization, partner)) %> - <%= live_patch("Edit", to: Routes.partner_index_path(@socket, :edit, @current_organization, partner)) %> - <%= link("Delete", to: "#", phx_click: "delete", phx_value_id: partner.id, data: [confirm: "Are you sure?"]) %> -
    +
    +
    +
    +
    +

    + <%= gettext("Partners") %> +

    +
    + +
    +
    +
    + +
    +
    +
    +
    -<%= live_patch("New Partner", to: Routes.partner_index_path(@socket, :new, @current_organization)) %> +
      + <%= for partner <- @partnerships do %> +
    • +
      +
      + <%= if partner.image do %> + + <% else %> + + + <%= Atomic.Accounts.extract_initials(partner.name) %> + + + <% end %> +
      +
      +

      <%= partner.name %>

      +

      <%= partner.description %>

      +
      +
      + +
    • + <% end %> +
    diff --git a/lib/atomic_web/router.ex b/lib/atomic_web/router.ex index 9de88ed12..77fd95463 100644 --- a/lib/atomic_web/router.ex +++ b/lib/atomic_web/router.ex @@ -50,6 +50,7 @@ defmodule AtomicWeb.Router do live_session :logged_in, on_mount: [{AtomicWeb.Hooks, :authenticated_user_state}] do live "/scanner", ScannerLive.Index, :index + live "/calendar", CalendarLive.Show, :show scope "/organizations/:organization_id" do pipe_through :admin diff --git a/lib/atomic_web/templates/error/404.html.heex b/lib/atomic_web/templates/error/404.html.heex index 3678ddf7d..b59691684 100644 --- a/lib/atomic_web/templates/error/404.html.heex +++ b/lib/atomic_web/templates/error/404.html.heex @@ -5,7 +5,7 @@ - <%= live_title_tag(assigns[:page_title] || "Store", suffix: " · 404") %> + <%= live_title_tag(assigns[:page_title] || "Atomic", suffix: " · 404") %> diff --git a/lib/atomic_web/templates/layout/live.html.heex b/lib/atomic_web/templates/layout/live.html.heex index 8b6bf1b49..f7d68c26f 100644 --- a/lib/atomic_web/templates/layout/live.html.heex +++ b/lib/atomic_web/templates/layout/live.html.heex @@ -1,5 +1,5 @@
    -
    +
    @@ -174,7 +174,7 @@
    diff --git a/lib/atomic_web/templates/layout/root.html.heex b/lib/atomic_web/templates/layout/root.html.heex index 805064944..e9c9e342e 100644 --- a/lib/atomic_web/templates/layout/root.html.heex +++ b/lib/atomic_web/templates/layout/root.html.heex @@ -1,5 +1,5 @@ - + diff --git a/lib/atomic_web/views/calendar_utils.ex b/lib/atomic_web/views/calendar_utils.ex new file mode 100644 index 000000000..596e97cac --- /dev/null +++ b/lib/atomic_web/views/calendar_utils.ex @@ -0,0 +1,63 @@ +defmodule AtomicWeb.CalendarUtils do + @moduledoc """ + Calendar utils functions to be used on all views. + """ + use Phoenix.HTML + use Timex + + def current_from_params(time_zone, %{"day" => day, "month" => month, "year" => year}) do + case Timex.parse("#{year}-#{month}-#{day}", "{YYYY}-{0M}-{D}") do + {:ok, current} -> + NaiveDateTime.to_date(current) + + _ -> + Timex.today(time_zone) + end + end + + def current_from_params(time_zone, %{"month" => month, "year" => year}) do + case Timex.parse("#{year}-#{month}-01", "{YYYY}-{0M}-{D}") do + {:ok, current} -> + NaiveDateTime.to_date(current) + + _ -> + Timex.today(time_zone) + end + end + + def current_from_params(time_zone, %{"day" => day, "month" => month, "year" => year}) do + case Timex.parse("#{year}-#{month}-#{day}", "{YYYY}-{Wiso}") do + {:ok, current} -> + NaiveDateTime.to_date(current) + + _ -> + Timex.today(time_zone) + end + end + + def current_from_params(time_zone, _) do + Timex.today(time_zone) + end + + def date_to_day(date_time) do + Timex.format!(date_time, "{D}") + end + + def date_to_week(date_time) do + Timex.format!(date_time, "{Wiso}") + end + + def date_to_month(date_time) do + Timex.format!(date_time, "{0M}") + end + + def date_to_year(date_time) do + Timex.format!(date_time, "{YYYY}") + end + + def get_date_sessions(sessions, date) do + sessions + |> Enum.filter(&(NaiveDateTime.to_date(&1.start) == date)) + |> Enum.sort_by(& &1.start, {:asc, NaiveDateTime}) + end +end diff --git a/lib/atomic_web/views/helpers.ex b/lib/atomic_web/views/helpers.ex index e93b2a8bf..b353b0ac3 100644 --- a/lib/atomic_web/views/helpers.ex +++ b/lib/atomic_web/views/helpers.ex @@ -149,6 +149,13 @@ defmodule AtomicWeb.ViewUtils do end end + def build_path(current_path, params) do + current_path + |> URI.parse() + |> Map.put(:query, URI.encode_query(params)) + |> URI.to_string() + end + @doc ~S""" Returns an error message for a given error From 1d9331d5632441960d1728c2cfd08a5fe7137434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Rodrigues?= Date: Fri, 4 Aug 2023 16:25:41 +0100 Subject: [PATCH 04/26] Add login/register pages --- assets/tailwind.config.js | 3 + .../live/department_live/show.html.heex | 52 ++++++++---- lib/atomic_web/live/home_live/index.html.heex | 6 +- .../templates/user_registration/new.html.heex | 61 +++++++++------ .../templates/user_session/new.html.heex | 74 +++++++++++++----- priv/static/images/logo.png | Bin 0 -> 230564 bytes .../user_registration_controller_test.exs | 4 +- .../user_session_controller_test.exs | 4 +- 8 files changed, 136 insertions(+), 68 deletions(-) create mode 100644 priv/static/images/logo.png diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 080b95ea1..b591d2b04 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -14,6 +14,9 @@ module.exports = { border: { '1': '1px' }, + backgroundImage: { + 'hero-pattern': "url('/images/logo.png')", + }, }, }, plugins: [ diff --git a/lib/atomic_web/live/department_live/show.html.heex b/lib/atomic_web/live/department_live/show.html.heex index a48601716..f748238f3 100644 --- a/lib/atomic_web/live/department_live/show.html.heex +++ b/lib/atomic_web/live/department_live/show.html.heex @@ -6,23 +6,41 @@ <% end %> -
      -
    • - Name: - <%= @department.name %> -
    • - -
    • - Activities: -
        - <%= for activity <- @department.activities do %> -
      • - <%= live_redirect(activity.title, to: Routes.activity_show_path(@socket, :show, @current_organization, activity)) %> -
      • - <% end %> -
      -
    • -
    +
    +
    +
    +
    +
    +
    +

    + <%= @department.name %> +

    +
    +
    +
    +
    +
    + Activities: +
      + <%= for activity <- @department.activities do %> +
    • + <%= live_patch to: Routes.activity_show_path(@socket, :show, @current_organization, activity), class: "" do %> +
      +

      <%= activity.title %>

      +
      +
      +
      + <%= activity.description %> +
      + random image +
      + <% end %> +
    • + <% end %> +
    +
    +
    +
    <%= live_patch("Edit", to: Routes.department_show_path(@socket, :edit, @current_organization, @department), class: "button") %> | <%= live_redirect("Back", to: Routes.department_index_path(@socket, :index, @current_organization)) %> diff --git a/lib/atomic_web/live/home_live/index.html.heex b/lib/atomic_web/live/home_live/index.html.heex index 53f656366..d0518a016 100644 --- a/lib/atomic_web/live/home_live/index.html.heex +++ b/lib/atomic_web/live/home_live/index.html.heex @@ -42,7 +42,7 @@
    -
    +

    <%= gettext("Partners") %> @@ -90,8 +90,8 @@

    <.calendar id="calendar" current_path={Routes.calendar_show_path(@socket, :show)} sessions={@sessions} params={@params} mode={@mode} time_zone={@time_zone} current_organization={@current_organization} />
    -
    - +
    diff --git a/lib/atomic_web/live/organization_live/show.html.heex b/lib/atomic_web/live/organization_live/show.html.heex index 7183a9e06..ffdfd00c8 100644 --- a/lib/atomic_web/live/organization_live/show.html.heex +++ b/lib/atomic_web/live/organization_live/show.html.heex @@ -17,15 +17,28 @@ <% end %>
    -
    -

    - <%= @organization.name %> - <%= if not @following do %> - - <% else %> - +
    + <%= if Organizations.get_role(@current_user.id, @organization.id) in [:owner, :admin] do %> + <%= live_patch to: Routes.organization_index_path(@socket, :edit, @organization), class: "button" do %> + <% end %> -

    + <%= link to: "#", phx_click: "delete", phx_value_id: @organization.id, data: [confirm: "Are you sure?"] do %> + + <% end %> + <% else %> +

    + <%= @organization.name %> + <%= if not @following do %> + + <% else %> + + <% end %> +

    + <% end %>
    <%= @organization.description %> diff --git a/lib/atomic_web/live/scanner_live/index.ex b/lib/atomic_web/live/scanner_live/index.ex index 6e8d5ab64..e7b8580c0 100644 --- a/lib/atomic_web/live/scanner_live/index.ex +++ b/lib/atomic_web/live/scanner_live/index.ex @@ -26,6 +26,15 @@ defmodule AtomicWeb.ScannerLive.Index do |> assign(:breadcrumb_entries, entries)} end + @doc """ + Handles the scan event. + Basically it does two checks: + 1) Verifies if current_organization is in organizations that are related to the session of the activity. + 2) Verifies if current_user is admin or owner of the organization, or , if current_user is admin of the system. + + If 1) and 2) are true, then confirm_participation is called. + Else a flash message is shown and the user is redirected to the scanner index. + """ @impl true def handle_event("scan", pathname, socket) do [_, session_id, user_id | _] = String.split(pathname, "/") @@ -47,6 +56,9 @@ defmodule AtomicWeb.ScannerLive.Index do end end + # Updates the enrollment of the user in the session, setting present to true. + # If the update is successful, a flash message is shown and the user is redirected to the scanner index. + # Else a flash message is shown and the user is redirected to the scanner index. defp confirm_participation(socket, session_id, user_id) do case Activities.update_enrollment(Activities.get_enrollment!(session_id, user_id), %{ present: true diff --git a/test/atomic/organizations_test.exs b/test/atomic/organizations_test.exs index 12c9320d4..21b05806e 100644 --- a/test/atomic/organizations_test.exs +++ b/test/atomic/organizations_test.exs @@ -13,7 +13,7 @@ defmodule Atomic.OrganizationsTest do organization = insert(:organization) organizations = - Organizations.list_organizations() + Organizations.list_organizations([]) |> Enum.map(& &1.id) assert organizations == [organization.id] From b804ae5e17518167f314e1c696267460664a0787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Rodrigues?= Date: Wed, 9 Aug 2023 18:37:42 +0100 Subject: [PATCH 22/26] Add boards --- assets/js/app.js | 4 +- assets/js/sorting.js | 17 ++ assets/vendor/sortable.js | 2 + lib/atomic/board.ex | 281 ++++++++++++++++++ lib/atomic/organizations/board.ex | 26 ++ lib/atomic/organizations/board_departments.ex | 26 ++ lib/atomic/organizations/organization.ex | 7 + .../organizations/users_organizations.ex | 9 +- lib/atomic_web/live/board_live/index.ex | 22 +- .../live/board_live/index.html.heex | 133 ++------- lib/atomic_web/live/board_live/new.ex | 6 +- .../20230980102641_create_board.exs | 14 + ...0230980102645_create_board_departments.exs | 14 + ...0231030141755_add_users_organizations.exs} | 12 +- priv/repo/seeds/memberships.exs | 72 +++-- test/atomic/organizations_test.exs | 4 +- .../factories/organizations_factory.ex | 21 +- .../fixtures/organizations_fixtures.ex | 72 ++++- 18 files changed, 576 insertions(+), 166 deletions(-) create mode 100644 assets/js/sorting.js create mode 100644 assets/vendor/sortable.js create mode 100644 lib/atomic/board.ex create mode 100644 lib/atomic/organizations/board.ex create mode 100644 lib/atomic/organizations/board_departments.ex create mode 100644 priv/repo/migrations/20230980102641_create_board.exs create mode 100644 priv/repo/migrations/20230980102645_create_board_departments.exs rename priv/repo/migrations/{20230330141755_add_users_organizations.exs => 20231030141755_add_users_organizations.exs} (50%) diff --git a/assets/js/app.js b/assets/js/app.js index 4dfe4899f..65f14d196 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -25,9 +25,11 @@ import {LiveSocket} from "phoenix_live_view" import "../vendor/alpine.js"; import topbar from "../vendor/topbar" import { QrScanner } from "./qr_reading.js"; +import { InitSorting } from "./sorting.js"; let Hooks = { - QrScanner: QrScanner + QrScanner: QrScanner, + InitSorting: InitSorting }; let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") diff --git a/assets/js/sorting.js b/assets/js/sorting.js new file mode 100644 index 000000000..4cd391e0f --- /dev/null +++ b/assets/js/sorting.js @@ -0,0 +1,17 @@ +import Sortable from "../vendor/sortable.js" + +export const InitSorting = { + mounted() { + new Sortable(this.el, { + animation: 150, + ghostClass: "bg-slate-100", + dragClass: "shadow-2xl", + handle: ".handle", + onEnd: (evt) => { + const elements = Array.from(this.el.children) + const ids = elements.map(elm => elm.id) + this.pushEvent("update-sorting", {ids: ids}) + } + }) + } +} \ No newline at end of file diff --git a/assets/vendor/sortable.js b/assets/vendor/sortable.js new file mode 100644 index 000000000..17bb16c73 --- /dev/null +++ b/assets/vendor/sortable.js @@ -0,0 +1,2 @@ +/*! Sortable 1.15.0 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function M(o){for(var t=1;tt.length)&&(e=t.length);for(var n=0,o=new Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function N(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&p(t,e)||o&&t===n)return t}while(t!==n&&(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode))}var i;return null}var g,m=/\s+/g;function I(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(m," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(m," ")))}function P(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=P(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function b(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[j]._onDragOver(o)}}var i,r,a}function Yt(t){q&&q.parentNode[j]._isOutsideThisEl(t.target)}function Bt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[j]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return It(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Bt.supportPointer&&"PointerEvent"in window&&!u,emptyInsertThreshold:5};for(n in K.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in Pt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&Mt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),Et.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,x())}function Ft(t,e,n,o,i,r,a,l){var s,c,u=t[j],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||k(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function jt(t){t.draggable=!1}function Ht(){Ct=!1}function Lt(t){return setTimeout(t,0)}function Kt(t){return clearTimeout(t)}Bt.prototype={constructor:Bt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(gt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,q):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){Tt.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&Tt.push(o)}}(o),!q&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=N(l,t.draggable,o,!1))&&l.animated||J===l)){if(nt=B(l),it=B(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return U({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),z("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=N(s,t.trim(),o,!1))return U({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),z("filter",n,{evt:e}),!0}))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!N(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!q&&n.parentNode===r&&(o=k(n),$=r,V=(q=n).parentNode,Q=q.nextSibling,J=n,at=a.group,st={target:Bt.dragged=q,clientX:(e||t).clientX,clientY:(e||t).clientY},ht=st.clientX-o.left,ft=st.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,q.style["will-change"]="all",o=function(){z("delayEnded",i,{evt:t}),Bt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(q.draggable=!0),i._triggerDragStart(t,e),U({sortable:i,name:"choose",originalEvent:t}),I(q,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){b(q,t.trim(),jt)}),h(l,"dragover",Xt),h(l,"mousemove",Xt),h(l,"touchmove",Xt),h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,q.draggable=!0),z("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():Bt.eventCanceled?this._onDrop():(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){q&&jt(q),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;f(t,"mouseup",this._disableDelayedDrag),f(t,"touchend",this._disableDelayedDrag),f(t,"touchcancel",this._disableDelayedDrag),f(t,"mousemove",this._delayedDragTouchMoveHandler),f(t,"touchmove",this._delayedDragTouchMoveHandler),f(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(q,"dragend",this),h($,"dragstart",this._onDragStart));try{document.selection?Lt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;yt=!1,$&&q?(z("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Yt),n=this.options,t||I(q,n.dragClass,!1),I(q,n.ghostClass,!0),Bt.active=this,t&&this._appendGhost(),U({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(ct){this._lastX=ct.clientX,this._lastY=ct.clientY,kt();for(var t=document.elementFromPoint(ct.clientX,ct.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(ct.clientX,ct.clientY))!==e;)e=t;if(q.parentNode[j]._isOutsideThisEl(t),e)do{if(e[j])if(e[j]._onDragOver({clientX:ct.clientX,clientY:ct.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=(t=e).parentNode);Rt()}},_onTouchMove:function(t){if(st){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=Z&&v(Z,!0),a=Z&&r&&r.a,l=Z&&r&&r.d,e=Ot&&bt&&E(bt),a=(i.clientX-st.clientX+o.x)/(a||1)+(e?e[0]-_t[0]:0)/(a||1),l=(i.clientY-st.clientY+o.y)/(l||1)+(e?e[1]-_t[1]:0)/(l||1);if(!Bt.active&&!yt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))n.right+10||t.clientX<=n.right&&t.clientY>n.bottom&&t.clientX>=n.left:t.clientX>n.right&&t.clientY>n.top||t.clientX<=n.right&&t.clientY>n.bottom+10}(n,r,this)&&!g.animated){if(g===q)return O(!1);if((l=g&&a===n.target?g:l)&&(w=k(l)),!1!==Ft($,a,q,o,l,w,n,!!l))return x(),g&&g.nextSibling?a.insertBefore(q,g.nextSibling):a.appendChild(q),V=a,A(),O(!0)}else if(g&&function(t,e,n){n=k(X(n.el,0,n.options,!0));return e?t.clientX list_boards() + [%Board{}, ...] + + """ + def list_boards do + Repo.all(Board) + end + + @doc """ + Returns the list of boards belonging to an organization. + + ## Examples + + iex> list_boards_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621) + [%Board{}, ...] + + """ + def list_boards_by_organization_id(id) do + Repo.all(from d in Board, where: d.organization_id == ^id) + end + + @doc """ + Returns the list of boards in a list of given ids. + + ## Examples + + iex> get_boards([99d7c9e5-4212-4f59-a097-28aaa33c2621, 99d7c9e5-4212-4f59-a097-28aaa33c2621]) + [%Board{}, ...] + + iex> get_boards(nil) + [] + """ + def get_boards(nil), do: [] + + def get_boards(ids) do + Repo.all(from d in Board, where: d.id in ^ids) + end + + @doc """ + Gets a single board. + + Raises `Ecto.NoResultsError` if the Board does not exist. + + ## Examples + + iex> get_board!(123) + %Board{} + + iex> get_board!(456) + ** (Ecto.NoResultsError) + + """ + def get_board!(id, opts \\ []) do + Board + |> apply_filters(opts) + |> Repo.get!(id) + end + + @doc """ + Creates a board. + + ## Examples + + iex> create_board(%{field: value}) + {:ok, %Board{}} + + iex> create_board(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_board(attrs \\ %{}) do + %Board{} + |> Board.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a board. + + ## Examples + + iex> update_board(board, %{field: new_value}) + {:ok, %Board{}} + + iex> update_board(board, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_board(%Board{} = board, attrs) do + board + |> Board.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a board. + + ## Examples + + iex> delete_board(board) + {:ok, %Board{}} + + iex> delete_board(board) + {:error, %Ecto.Changeset{}} + + """ + def delete_board(%Board{} = board) do + Repo.delete(board) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking board changes. + + ## Examples + + iex> change_board(board) + %Ecto.Changeset{data: %Board{}} + + """ + def change_board(%Board{} = board, attrs \\ %{}) do + Board.changeset(board, attrs) + end + + def get_organization_board_by_year(year, organization_id) do + Repo.get_by(Board, year: year, organization_id: organization_id) + end + + @doc """ + Returns the list of board_departments. + + ## Examples + + iex> list_board_departments() + [%BoardDepartments{}, ...] + + """ + def list_board_departments do + Repo.all(BoardDepartments) + end + + @doc """ + Returns the list of board_departments belonging to an organization. + + ## Examples + + iex> list_board_departments_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621) + [%BoardDepartments{}, ...] + + """ + def list_board_departments_by_organization_id(id) do + Repo.all(from d in BoardDepartments, where: d.organization_id == ^id) + end + + @doc """ + Returns the list of board_departments in a list of given ids. + + ## Examples + + iex> get_board_departments([99d7c9e5-4212-4f59-a097-28aaa33c2621, 99d7c9e5-4212-4f59-a097-28aaa33c2621]) + [%BoardDepartments{}, ...] + + iex> get_board_departments(nil) + [] + """ + def get_board_departments(nil), do: [] + + def get_board_departments(ids) do + Repo.all(from d in BoardDepartments, where: d.id in ^ids) + end + + def get_board_departments_by_board_id(board_id, _preloads \\ []) do + Repo.all(from d in BoardDepartments, where: d.board_id == ^board_id) + |> Enum.sort_by(& &1.priority) + end + + def get_board_department_users(board_departments_id) do + Repo.all(from d in UserOrganization, where: d.board_departments_id == ^board_departments_id) + |> Repo.preload(:user) + |> Enum.sort_by(& &1.priority) + end + + @doc """ + Gets a single board_departments. + + Raises `Ecto.NoResultsError` if the BoardDepartments does not exist. + + ## Examples + + iex> get_board_department!(123) + %BoardDepartments{} + + iex> get_board_department!(456) + ** (Ecto.NoResultsError) + + """ + def get_board_department!(id, opts \\ []) do + BoardDepartments + |> apply_filters(opts) + |> Repo.get!(id) + end + + @doc """ + Creates a board_departments. + + ## Examples + + iex> create_board_department(%{field: value}) + {:ok, %BoardDepartments{}} + + iex> create_board_department(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_board_department(attrs \\ %{}) do + %BoardDepartments{} + |> BoardDepartments.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a board_departments. + + ## Examples + + iex> update_board_department(board_departments, %{field: new_value}) + {:ok, %BoardDepartments{}} + + iex> update_board_department(board_departments, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_board_department(%BoardDepartments{} = board_departments, attrs) do + board_departments + |> BoardDepartments.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a board_departments. + + ## Examples + + iex> delete_board_department(board_departments) + {:ok, %BoardDepartments{}} + + iex> delete_board_department(board_departments) + {:error, %Ecto.Changeset{}} + + """ + def delete_board_department(%BoardDepartments{} = board_departments) do + Repo.delete(board_departments) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking board_departments changes. + + ## Examples + + iex> change_board_department(board_departments) + %Ecto.Changeset{data: %BoardDepartments{}} + + """ + def change_board_department(%BoardDepartments{} = board_departments, attrs \\ %{}) do + BoardDepartments.changeset(board_departments, attrs) + end +end diff --git a/lib/atomic/organizations/board.ex b/lib/atomic/organizations/board.ex new file mode 100644 index 000000000..76438bb73 --- /dev/null +++ b/lib/atomic/organizations/board.ex @@ -0,0 +1,26 @@ +defmodule Atomic.Organizations.Board do + @moduledoc false + use Atomic.Schema + + alias Atomic.Ecto.Year + alias Atomic.Organizations.BoardDepartments + alias Atomic.Organizations.Organization + + @required_fields ~w(year organization_id)a + + schema "board" do + field :year, Year + has_many :board_departments, BoardDepartments + + belongs_to :organization, Organization + + timestamps() + end + + @doc false + def changeset(board, attrs) do + board + |> cast(attrs, @required_fields) + |> validate_required(@required_fields) + end +end diff --git a/lib/atomic/organizations/board_departments.ex b/lib/atomic/organizations/board_departments.ex new file mode 100644 index 000000000..52f6fba9f --- /dev/null +++ b/lib/atomic/organizations/board_departments.ex @@ -0,0 +1,26 @@ +defmodule Atomic.Organizations.BoardDepartments do + @moduledoc false + use Atomic.Schema + + alias Atomic.Organizations.Board + alias Atomic.Organizations.UserOrganization + + @required_fields ~w(name board_id priority)a + + schema "board_departments" do + field :name, :string + field :priority, :integer + + has_many :user_organization, UserOrganization + belongs_to :board, Board + + timestamps() + end + + @doc false + def changeset(board_departments, attrs) do + board_departments + |> 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 08bc5245a..4019d4eff 100644 --- a/lib/atomic/organizations/organization.ex +++ b/lib/atomic/organizations/organization.ex @@ -4,6 +4,7 @@ defmodule Atomic.Organizations.Organization do alias Atomic.Accounts.User alias Atomic.Activities.Location alias Atomic.News.New + alias Atomic.Organizations.Board alias Atomic.Organizations.Card alias Atomic.Organizations.Department alias Atomic.Organizations.Membership @@ -40,6 +41,12 @@ defmodule Atomic.Organizations.Organization do on_replace: :delete, preload_order: [asc: :inserted_at] + has_many :boards, Board, + on_replace: :delete_if_exists, + on_delete: :delete_all, + foreign_key: :organization_id, + preload_order: [asc: :year] + timestamps() end diff --git a/lib/atomic/organizations/users_organizations.ex b/lib/atomic/organizations/users_organizations.ex index e49c06d45..0c80f4c4c 100644 --- a/lib/atomic/organizations/users_organizations.ex +++ b/lib/atomic/organizations/users_organizations.ex @@ -3,17 +3,16 @@ defmodule Atomic.Organizations.UserOrganization do use Atomic.Schema alias Atomic.Accounts.User - alias Atomic.Ecto.Year - alias Atomic.Organizations.Organization + alias Atomic.Organizations.BoardDepartments - @required_fields ~w(year title user_id organization_id)a + @required_fields ~w(title priority user_id board_departments_id)a schema "users_organizations" do - field :year, Year field :title, :string + field :priority, :integer belongs_to :user, User - belongs_to :organization, Organization + belongs_to :board_departments, BoardDepartments timestamps() end diff --git a/lib/atomic_web/live/board_live/index.ex b/lib/atomic_web/live/board_live/index.ex index d86891095..1e79d16e6 100644 --- a/lib/atomic_web/live/board_live/index.ex +++ b/lib/atomic_web/live/board_live/index.ex @@ -1,6 +1,7 @@ defmodule AtomicWeb.BoardLive.Index do use AtomicWeb, :live_view + alias Atomic.Board alias Atomic.Organizations @impl true @@ -10,8 +11,10 @@ defmodule AtomicWeb.BoardLive.Index do @impl true def handle_params(%{"organization_id" => id}, _, socket) do - users_organizations = list_users_organizations(id) + board = Board.get_organization_board_by_year("2023/2024", id) + board_departments = Board.get_board_departments_by_board_id(board.id) organization = Organizations.get_organization!(id) + role = Organizations.get_role(socket.assigns.current_user.id, id) entries = [ %{ @@ -24,11 +27,26 @@ defmodule AtomicWeb.BoardLive.Index do socket |> assign(:current_page, :board) |> assign(:breadcrumb_entries, entries) + |> assign(:board_departments, board_departments) |> assign(:page_title, page_title(socket.assigns.live_action, organization)) - |> assign(:users_organizations, users_organizations) + |> assign(:role, role) |> assign(:id, id)} end + def handle_event("update-sorting", %{"ids" => ids}, socket) do + ids = Enum.filter(ids, fn id -> String.length(id) > 0 end) + + ids + |> Enum.with_index(0) + |> Enum.each(fn {"board-department-" <> id, priority} -> + id + |> Board.get_board_department!() + |> Board.update_board_department(%{priority: priority}) + end) + + {:noreply, socket} + end + @impl true def handle_event("delete", %{"id" => id}, socket) do user_organization = Organizations.get_user_organization!(id) diff --git a/lib/atomic_web/live/board_live/index.html.heex b/lib/atomic_web/live/board_live/index.html.heex index 8e8b9179f..3505b54ee 100644 --- a/lib/atomic_web/live/board_live/index.html.heex +++ b/lib/atomic_web/live/board_live/index.html.heex @@ -1,5 +1,5 @@
    -
    +

      @@ -13,113 +13,26 @@ ) %>

      -

      Presidência

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Vogais

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Departamento de Centro de Apoio ao Open Source

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Departamento de Marketing e Conteúdo

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Departamento de Merch e Parcerias

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Departamento Recreativo

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Departamento Pedagógico

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Mesa da Assembleia Geral

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -

      Conselho Fiscal

      -
        - <%= for user_organization <- Enum.take(@users_organizations,5) do %> - <%= live_patch to: Routes.board_show_path(@socket, :show, user_organization.organization_id, user_organization) do %> -
      • - -

        Michael Foster

        -

        Co-Founder / CTO

        -
      • - <% end %> - <% end %> -
      -
    + <%= for board_department <- @board_departments do %> +
  • +
    +

    <%= board_department.name %>

    + <%= if @role in [:owner, :admin] do %> + + + + <% end %> +
    +
    + <%= for user_organization <- Board.get_board_department_users(board_department.id) do %> +
      + +

      <%= user_organization.user.name %>

      +

      <%= user_organization.title %>

      +
    + <% end %> +
    +
  • + <% end %> +
    diff --git a/lib/atomic_web/live/board_live/new.ex b/lib/atomic_web/live/board_live/new.ex index e7aee6840..49f1d4bdd 100644 --- a/lib/atomic_web/live/board_live/new.ex +++ b/lib/atomic_web/live/board_live/new.ex @@ -11,13 +11,11 @@ defmodule AtomicWeb.BoardLive.New do end @impl true - def handle_params(%{"organization_id" => organization_id}, _url, socket) do + def handle_params(%{"organization_id" => _organization_id}, _url, socket) do {:noreply, socket |> assign(:page_title, gettext("New Board")) - |> assign(:user_organization, %UserOrganization{ - organization_id: organization_id - }) + |> assign(:user_organization, %UserOrganization{}) |> assign(:users, Enum.map(Accounts.list_users(), fn u -> [key: u.email, value: u.id] end)) |> assign(:current_user, socket.assigns.current_user)} end diff --git a/priv/repo/migrations/20230980102641_create_board.exs b/priv/repo/migrations/20230980102641_create_board.exs new file mode 100644 index 000000000..6d0b79d17 --- /dev/null +++ b/priv/repo/migrations/20230980102641_create_board.exs @@ -0,0 +1,14 @@ +defmodule Atomic.Repo.Migrations.CreateBoard do + use Ecto.Migration + + def change do + create table(:board, primary_key: false) do + add :id, :binary_id, primary_key: true + add :year, :string, null: false + add :organization_id, references(:organizations, type: :binary_id, null: false) + timestamps() + end + + create unique_index(:board, [:year, :organization_id]) + end +end diff --git a/priv/repo/migrations/20230980102645_create_board_departments.exs b/priv/repo/migrations/20230980102645_create_board_departments.exs new file mode 100644 index 000000000..45df8fcde --- /dev/null +++ b/priv/repo/migrations/20230980102645_create_board_departments.exs @@ -0,0 +1,14 @@ +defmodule Atomic.Repo.Migrations.CreateBoardDepartments do + use Ecto.Migration + + def change do + create table(:board_departments, primary_key: false) do + add :id, :binary_id, primary_key: true + add :name, :string + add :priority, :integer + add :board_id, references(:board, type: :binary_id, null: false) + + timestamps() + end + end +end diff --git a/priv/repo/migrations/20230330141755_add_users_organizations.exs b/priv/repo/migrations/20231030141755_add_users_organizations.exs similarity index 50% rename from priv/repo/migrations/20230330141755_add_users_organizations.exs rename to priv/repo/migrations/20231030141755_add_users_organizations.exs index 50f9c66f0..4fe4a7ead 100644 --- a/priv/repo/migrations/20230330141755_add_users_organizations.exs +++ b/priv/repo/migrations/20231030141755_add_users_organizations.exs @@ -5,17 +5,15 @@ defmodule Atomic.Repo.Migrations.AddUsersOrganizations do create table(:users_organizations, primary_key: false) do add :id, :binary_id, primary_key: true - add :year, :string, null: false - add :title, :string, null: false + add :title, :string + add :priority, :integer - add :user_id, references(:users, on_delete: :nothing, type: :binary_id), null: false + add :user_id, references(:users, on_delete: :nothing, type: :binary_id) - add :organization_id, references(:organizations, on_delete: :nothing, type: :binary_id), - null: false + add :board_departments_id, + references(:board_departments, on_delete: :nothing, type: :binary_id) timestamps() end - - create unique_index(:users_organizations, [:year, :organization_id, :user_id]) end end diff --git a/priv/repo/seeds/memberships.exs b/priv/repo/seeds/memberships.exs index d6d16fd75..5f1640c3b 100644 --- a/priv/repo/seeds/memberships.exs +++ b/priv/repo/seeds/memberships.exs @@ -3,11 +3,16 @@ defmodule Atomic.Repo.Seeds.Memberships do alias Atomic.Organizations.Membership alias Atomic.Accounts.User alias Atomic.Organizations + alias Atomic.Organizations.Board + alias Atomic.Organizations.BoardDepartments + alias Atomic.Organizations.UserOrganization alias Atomic.Repo def run do seed_memberships() - seed_users_organizations() + seed_board() + seed_board_departments() + seed_user_organizations() end def seed_memberships() do @@ -33,34 +38,51 @@ defmodule Atomic.Repo.Seeds.Memberships do end end - def seed_users_organizations() do - years = ["2019/2020", "2020/2021", "2021/2022", "2022/2023", "2023/2024"] + def seed_board do organizations = Repo.all(Organization) - users = Repo.all(User) - titles = [ - "Presidente", - "Vice-Presidente", - "Tesoureiro", - "Secretário", - "Presidente da MAG", - "Secretário da MAG", - "Presidente do CF", - "Secretário do CF" - ] + for organization <- organizations do + %Board{} + |> Board.changeset(%{ + "organization_id" => organization.id, + "year" => "2023/2024" + }) + |> Repo.insert!() + end + end - len_titles = length(titles) + def seed_board_departments do + departments = ["Conselho Fiscal", "Mesa AG", "CAOS", "DMC", "DMP", "Presidência", "Vogais"] + boards = Repo.all(Board) - for year <- years do - for org <- organizations do - for {user, title} <- Enum.zip(Enum.take_random(users, len_titles), titles) do - Organizations.create_user_organization(%{ - "year" => year, - "organization_id" => org.id, - "user_id" => user.id, - "title" => title - }) - end + for board <- boards do + for i <- 0..6 do + %BoardDepartments{} + |> BoardDepartments.changeset(%{ + "board_id" => board.id, + "name" => Enum.at(departments, i), + "priority" => i + }) + |> Repo.insert!() + end + end + end + + def seed_user_organizations do + titles = ["Presidente", "Vice-Presidente", "Secretário", "Tesoureiro", "Vogal"] + board_departments = Repo.all(BoardDepartments) + users = Repo.all(User) + + for board_department <- board_departments do + for i <- 0..4 do + %UserOrganization{} + |> UserOrganization.changeset(%{ + "user_id" => Enum.random(users).id, + "board_departments_id" => board_department.id, + "title" => Enum.at(titles, i), + "priority" => i + }) + |> Repo.insert!() end end end diff --git a/test/atomic/organizations_test.exs b/test/atomic/organizations_test.exs index 21b05806e..f0e7ff3c6 100644 --- a/test/atomic/organizations_test.exs +++ b/test/atomic/organizations_test.exs @@ -171,10 +171,12 @@ defmodule Atomic.OrganizationsTest do test "update_user_organization/2 updates existing user_organization" do user_organization = insert(:user_organization) + board_department = insert(:board_department) {:ok, new_user_organization} = Organizations.update_user_organization(user_organization, %{ - title: "Vice-Presidente" + title: "Vice-Presidente", + board_departments_id: board_department.id }) assert new_user_organization.title == "Vice-Presidente" diff --git a/test/support/factories/organizations_factory.ex b/test/support/factories/organizations_factory.ex index 7227e5405..aa317277b 100644 --- a/test/support/factories/organizations_factory.ex +++ b/test/support/factories/organizations_factory.ex @@ -3,7 +3,7 @@ defmodule Atomic.Factories.OrganizationFactory do A factory to generate account related structs """ - alias Atomic.Organizations.{Membership, Organization, UserOrganization} + alias Atomic.Organizations.{Board, BoardDepartments, Membership, Organization, UserOrganization} defmacro __using__(_opts) do quote do @@ -25,12 +25,27 @@ defmodule Atomic.Factories.OrganizationFactory do } end + def board_factory do + %Board{ + year: "2023/2024", + organization: build(:organization) + } + end + + def board_department_factory do + %BoardDepartments{ + name: Faker.Company.buzzword(), + priority: Enum.random(0..4), + board: build(:board) + } + end + def user_organization_factory do %UserOrganization{ user: build(:user), title: Faker.Company.bullshit(), - organization: build(:organization), - year: "2021/2022" + board_departments_id: build(:board_department).id, + priority: Enum.random(0..4) } end end diff --git a/test/support/fixtures/organizations_fixtures.ex b/test/support/fixtures/organizations_fixtures.ex index d20f98187..3cbf40e83 100644 --- a/test/support/fixtures/organizations_fixtures.ex +++ b/test/support/fixtures/organizations_fixtures.ex @@ -53,9 +53,50 @@ defmodule Atomic.OrganizationsFixtures do membership end - @doc """ - Generate an user organization. - """ + def board_fixture do + {:ok, organization} = + %{ + description: "some description", + name: "some name" + } + |> Atomic.Organizations.create_organization() + + {:ok, board} = + %{ + year: "2022/2023", + organization_id: organization.id + } + |> Atomic.Board.create_board() + + board + end + + def board_department_fixture do + {:ok, organization} = + %{ + description: "some description", + name: "some name" + } + |> Atomic.Organizations.create_organization() + + {:ok, board} = + %{ + year: "2022/2023", + organization_id: organization.id + } + |> Atomic.Board.create_board() + + {:ok, board_department} = + %{ + name: "some name", + board_id: board.id, + priority: 1 + } + |> Atomic.Board.create_board_department() + + board_department + end + def user_organization_fixture do {:ok, organization} = %{ @@ -64,20 +105,35 @@ defmodule Atomic.OrganizationsFixtures do } |> Atomic.Organizations.create_organization() + {:ok, board} = + %{ + year: "2022/2023", + organization_id: organization.id + } + |> Atomic.Board.create_board() + + {:ok, board_department} = + %{ + name: "some name", + board_id: board.id, + priority: 1 + } + |> Atomic.Board.create_board_department() + {:ok, user} = %{ - email: "email@mail.pt", + email: "test@mail.com", password: "password1234", - role: "student" + role: :student } |> Atomic.Accounts.register_user() {:ok, user_organization} = %{ - title: "Presidente", - year: "2022/2023", user_id: user.id, - organization_id: organization.id + board_departments_id: board_department.id, + title: "some title", + priority: 1 } |> Atomic.Organizations.create_user_organization() From 9dab7ba9aaf6601ff691d6efd49fb249d90570fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Rodrigues?= Date: Wed, 9 Aug 2023 22:12:27 +0100 Subject: [PATCH 23/26] Fix some issues when running without seeds --- lib/atomic/accounts/user.ex | 6 +- lib/atomic_web/components/organizations.ex | 2 +- lib/atomic_web/config.ex | 24 ++++- lib/atomic_web/controllers/user_auth.ex | 2 +- .../live/activity_live/index.html.heex | 14 +-- lib/atomic_web/live/board_live/index.ex | 8 +- .../live/board_live/index.html.heex | 14 +-- .../live/department_live/index.html.heex | 16 +-- lib/atomic_web/live/hooks.ex | 32 ++++-- .../live/organization_live/index.ex | 1 + lib/atomic_web/live/organization_live/show.ex | 13 ++- .../live/organization_live/show.html.heex | 8 +- .../live/partner_live/index.html.heex | 14 +-- .../templates/layout/live.html.heex | 98 ++++++++++--------- ...0221014155230_create_users_auth_tables.exs | 2 +- .../20230325151547_add_user_data.exs | 2 +- .../atomic_web/controllers/user_auth_test.exs | 6 +- 17 files changed, 160 insertions(+), 102 deletions(-) diff --git a/lib/atomic/accounts/user.ex b/lib/atomic/accounts/user.ex index 270c05804..2ef250c97 100644 --- a/lib/atomic/accounts/user.ex +++ b/lib/atomic/accounts/user.ex @@ -9,8 +9,8 @@ defmodule Atomic.Accounts.User do alias Atomic.Organizations.{Membership, Organization} alias Atomic.Uploaders.ProfilePicture - @required_fields ~w(email password role name)a - @optional_fields ~w(course_id default_organization_id)a + @required_fields ~w(email password)a + @optional_fields ~w(name course_id default_organization_id)a @roles ~w(admin student)a @@ -24,7 +24,7 @@ defmodule Atomic.Accounts.User do belongs_to :course, Course field :profile_picture, ProfilePicture.Type - field :role, Ecto.Enum, values: @roles + field :role, Ecto.Enum, values: @roles, default: :student has_many :enrollments, Enrollment diff --git a/lib/atomic_web/components/organizations.ex b/lib/atomic_web/components/organizations.ex index bb9a95e2b..df1cda6fb 100644 --- a/lib/atomic_web/components/organizations.ex +++ b/lib/atomic_web/components/organizations.ex @@ -17,7 +17,7 @@ defmodule AtomicWeb.Components.Organizations do ~H"""
    <%= for organization <- Accounts.get_user_organizations(current_user) do %> - <%= if organization.id != current_organization.id do %> + <%= if current_organization && organization.id != current_organization.id do %>
    diff --git a/lib/atomic_web/config.ex b/lib/atomic_web/config.ex index c27d53e47..71f1e3db2 100644 --- a/lib/atomic_web/config.ex +++ b/lib/atomic_web/config.ex @@ -6,14 +6,30 @@ defmodule AtomicWeb.Config do alias AtomicWeb.Router.Helpers, as: Routes def pages(conn, current_organization, current_user) do - if current_user.role in [:admin] or - Organizations.get_role(current_user.id, current_organization.id) in [:admin, :owner] do - admin_pages(conn, current_organization) + if current_organization do + if current_user.role in [:admin] or + Organizations.get_role(current_user.id, current_organization.id) in [:admin, :owner] do + admin_pages(conn, current_organization) + else + user_pages(conn, current_organization) + end else - user_pages(conn, current_organization) + default_pages(conn) end end + def default_pages(conn) do + [ + %{ + key: :organizations, + title: "Organizations", + icon: :office_building, + url: Routes.organization_index_path(conn, :index), + tabs: [] + } + ] + end + def admin_pages(conn, current_organization) do [ %{ diff --git a/lib/atomic_web/controllers/user_auth.ex b/lib/atomic_web/controllers/user_auth.ex index acae72d8a..5e1b9ea2d 100644 --- a/lib/atomic_web/controllers/user_auth.ex +++ b/lib/atomic_web/controllers/user_auth.ex @@ -39,7 +39,7 @@ defmodule AtomicWeb.UserAuth do |> put_session(:user_token, token) |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") |> maybe_write_remember_me_cookie(token, params) - |> redirect(to: user_return_to || signed_in_path(conn)) + |> redirect(to: "/organizations") _ -> conn diff --git a/lib/atomic_web/live/activity_live/index.html.heex b/lib/atomic_web/live/activity_live/index.html.heex index 9c53df290..345252b05 100644 --- a/lib/atomic_web/live/activity_live/index.html.heex +++ b/lib/atomic_web/live/activity_live/index.html.heex @@ -7,12 +7,14 @@ <%= gettext("Activities") %>
    - + <%= if Organizations.get_role(@current_user.id, @current_organization.id) in [:owner, :admin] or @current_user.role in [:admin] do %> + + <% end %>
    diff --git a/lib/atomic_web/live/board_live/index.ex b/lib/atomic_web/live/board_live/index.ex index 1e79d16e6..140145be3 100644 --- a/lib/atomic_web/live/board_live/index.ex +++ b/lib/atomic_web/live/board_live/index.ex @@ -12,7 +12,12 @@ defmodule AtomicWeb.BoardLive.Index do @impl true def handle_params(%{"organization_id" => id}, _, socket) do board = Board.get_organization_board_by_year("2023/2024", id) - board_departments = Board.get_board_departments_by_board_id(board.id) + board_departments = [] + + if board do + ^board_departments = Board.get_board_departments_by_board_id(board.id) + end + organization = Organizations.get_organization!(id) role = Organizations.get_role(socket.assigns.current_user.id, id) @@ -26,6 +31,7 @@ defmodule AtomicWeb.BoardLive.Index do {:noreply, socket |> assign(:current_page, :board) + |> assign(:current_organization, organization) |> assign(:breadcrumb_entries, entries) |> assign(:board_departments, board_departments) |> assign(:page_title, page_title(socket.assigns.live_action, organization)) diff --git a/lib/atomic_web/live/board_live/index.html.heex b/lib/atomic_web/live/board_live/index.html.heex index 3505b54ee..85e2f19c6 100644 --- a/lib/atomic_web/live/board_live/index.html.heex +++ b/lib/atomic_web/live/board_live/index.html.heex @@ -6,12 +6,14 @@ <%= gettext("Board") %>
    - + <%= if Organizations.get_role(@current_user.id, @current_organization.id) in [:owner, :admin] or @current_user.role in [:admin] do %> + + <% end %>
    <%= for board_department <- @board_departments do %>
  • diff --git a/lib/atomic_web/live/department_live/index.html.heex b/lib/atomic_web/live/department_live/index.html.heex index 7947f50b4..2127c4abb 100644 --- a/lib/atomic_web/live/department_live/index.html.heex +++ b/lib/atomic_web/live/department_live/index.html.heex @@ -11,12 +11,14 @@ <%= gettext("Departments") %>
  • - + <%= if Organizations.get_role(@current_user.id, @current_organization.id) in [:owner, :admin] or @current_user.role in [:admin] do %> + + <% end %>
    @@ -47,7 +49,7 @@ <%= department.description %>
    - <%= if Organizations.get_role(@current_user.id, @current_organization.id) in [:owner, :admin] do %> + <%= if Organizations.get_role(@current_user.id, @current_organization.id) in [:owner, :admin] or @current_user.role in [:admin] do %>
    diff --git a/lib/atomic_web/live/hooks.ex b/lib/atomic_web/live/hooks.ex index f1417d85d..98572e2ce 100644 --- a/lib/atomic_web/live/hooks.ex +++ b/lib/atomic_web/live/hooks.ex @@ -35,6 +35,7 @@ defmodule AtomicWeb.Hooks do else socket = socket + |> assign(:current_organization, nil) |> assign(:current_user, current_user) |> assign(:time_zone, time_zone) @@ -45,6 +46,7 @@ defmodule AtomicWeb.Hooks do def on_mount(:general_user_state, _params, session, socket) do current_organization = session["current_organization"] current_user = session["user_token"] + owner = Application.get_env(:atomic, :owner) time_zone = get_connect_params(socket)["timezone"] || owner.time_zone @@ -55,21 +57,31 @@ defmodule AtomicWeb.Hooks do {nil, _} -> user = Accounts.get_user_by_session_token(current_user) - {:cont, - socket - |> assign(:current_user, user) - |> assign( - :current_organization, - Organizations.get_organization!(user.default_organization_id) - ) - |> assign(:time_zone, time_zone)} + case user.default_organization_id do + nil -> + {:cont, + socket + |> assign(:current_user, user) + |> assign(:current_organization, nil) + |> assign(:time_zone, time_zone)} + + _ -> + {:cont, + socket + |> assign(:current_user, user) + |> assign( + :current_organization, + Organizations.get_organization!(user.default_organization_id) + ) + |> assign(:time_zone, time_zone)} + end {_, nil} -> {:cont, socket |> assign(:current_organization, current_organization)} - {organization, _} -> + {_, _} -> user = Accounts.get_user_by_session_token(current_user) {:cont, @@ -77,7 +89,7 @@ defmodule AtomicWeb.Hooks do |> assign(:current_user, user) |> assign( :current_organization, - organization + current_organization ) |> assign(:time_zone, time_zone)} end diff --git a/lib/atomic_web/live/organization_live/index.ex b/lib/atomic_web/live/organization_live/index.ex index 8226786d9..afb2bddde 100644 --- a/lib/atomic_web/live/organization_live/index.ex +++ b/lib/atomic_web/live/organization_live/index.ex @@ -26,6 +26,7 @@ defmodule AtomicWeb.OrganizationLive.Index do |> apply_action(socket.assigns.live_action, params) |> assign(:breadcrumb_entries, entries) |> assign(:params, params) + |> assign(:current_organization, socket.assigns.current_organization) |> assign(:organizations, organizations) |> assign(:current_page, :organizations)} end diff --git a/lib/atomic_web/live/organization_live/show.ex b/lib/atomic_web/live/organization_live/show.ex index 7ff285e88..2eb0ca498 100644 --- a/lib/atomic_web/live/organization_live/show.ex +++ b/lib/atomic_web/live/organization_live/show.ex @@ -12,7 +12,6 @@ defmodule AtomicWeb.OrganizationLive.Show do @impl true def mount(_params, session, socket) do user = Accounts.get_user_by_session_token(session["user_token"]) - {:ok, socket |> assign(:current_user, user)} end @@ -59,6 +58,8 @@ defmodule AtomicWeb.OrganizationLive.Show do organization_id: socket.assigns.organization.id } + current_user = socket.assigns.current_user + case Organizations.create_membership(attrs) do {:ok, _organization} -> {:noreply, @@ -69,6 +70,16 @@ defmodule AtomicWeb.OrganizationLive.Show do {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, :changeset, changeset)} end + + if current_user.default_organization_id == nil do + Accounts.update_user(current_user, %{ + default_organization_id: socket.assigns.organization.id + }) + end + + {:noreply, + socket + |> assign(:current_user, current_user)} end @impl true diff --git a/lib/atomic_web/live/organization_live/show.html.heex b/lib/atomic_web/live/organization_live/show.html.heex index ffdfd00c8..1a2d68a54 100644 --- a/lib/atomic_web/live/organization_live/show.html.heex +++ b/lib/atomic_web/live/organization_live/show.html.heex @@ -65,7 +65,7 @@

    <%= gettext("Activities") %>

    - <%= live_patch("See all", to: Routes.activity_index_path(@socket, :index, @current_organization), class: "text-sm font-semibold text-gray-300 ml-4") %> + <%= live_patch("See all", to: Routes.activity_index_path(@socket, :index, @organization), class: "text-sm font-semibold text-gray-300 ml-4") %>
    @@ -90,7 +90,7 @@ <% end %>
    - <.calendar id="calendar" current_path={Routes.calendar_show_path(@socket, :show)} sessions={@sessions} params={@params} mode={@mode} time_zone={@time_zone} current_organization={@current_organization} /> + <.calendar id="calendar" current_path={Routes.calendar_show_path(@socket, :show)} sessions={@sessions} params={@params} mode={@mode} time_zone={@time_zone} current_organization={@organization} />
    @@ -99,7 +99,7 @@