From 9a2fcb14a72643d23acb34e750dc3cb07273cd94 Mon Sep 17 00:00:00 2001 From: Nuno Miguel Date: Mon, 18 Mar 2024 11:35:55 +0000 Subject: [PATCH 1/6] feat: improve board page --- assets/js/sorting.js | 4 + .../live/board_live/form_component.ex | 81 ++++++++++------ .../live/board_live/form_component.html.heex | 10 +- lib/atomic_web/live/board_live/index.ex | 21 ++++ .../live/board_live/index.html.heex | 97 ++++++++++++++----- lib/atomic_web/live/board_live/new.ex | 6 +- lib/atomic_web/live/board_live/new.html.heex | 4 +- 7 files changed, 161 insertions(+), 62 deletions(-) diff --git a/assets/js/sorting.js b/assets/js/sorting.js index 4cd391e0f..e469bd147 100644 --- a/assets/js/sorting.js +++ b/assets/js/sorting.js @@ -2,6 +2,7 @@ import Sortable from "../vendor/sortable.js" export const InitSorting = { mounted() { + var placeholder = document.createElement('div'); new Sortable(this.el, { animation: 150, ghostClass: "bg-slate-100", @@ -11,6 +12,9 @@ export const InitSorting = { const elements = Array.from(this.el.children) const ids = elements.map(elm => elm.id) this.pushEvent("update-sorting", {ids: ids}) + }, + setData: (dataTransfer, _) => { + dataTransfer.setDragImage(placeholder, 0, 0); } }) } diff --git a/lib/atomic_web/live/board_live/form_component.ex b/lib/atomic_web/live/board_live/form_component.ex index 967aae4ee..8a69fc8d8 100644 --- a/lib/atomic_web/live/board_live/form_component.ex +++ b/lib/atomic_web/live/board_live/form_component.ex @@ -1,7 +1,8 @@ defmodule AtomicWeb.BoardLive.FormComponent do use AtomicWeb, :live_component - alias Atomic.Organizations + # alias Atomic.Organizations + alias Atomic.Board @impl true def mount(socket) do @@ -9,55 +10,71 @@ defmodule AtomicWeb.BoardLive.FormComponent do end @impl true - def update(%{user_organization: user_organization} = assigns, socket) do - changeset = Organizations.change_user_organization(user_organization) + def update(%{organization_id: _org} = assigns, socket) do + # changeset = Organizations.change_user_organization(user_organization)~ {:ok, socket - |> assign(assigns) - |> assign(:changeset, changeset)} + |> assign(assigns)} end @impl true - def handle_event("validate", %{"user_organization" => user_organization_params}, socket) do + def handle_event("validate", %{"board" => board_params}, socket) do changeset = socket.assigns.user_organization - |> Organizations.change_user_organization(user_organization_params) + # |> Organizations.change_user_organization(user_organization_params) + |> Board.change_board(board_params) |> Map.put(:action, :validate) {:noreply, assign(socket, :changeset, changeset)} end - def handle_event("save", %{"user_organization" => user_organization_params}, socket) do - save_user_organization(socket, socket.assigns.action, user_organization_params) + def handle_event("save", board_params, socket) do + # save_user_organization(socket, socket.assigns.action, user_organization_params) + create_board(socket, board_params) end - defp save_user_organization(socket, :edit, user_organization_params) do - case Organizations.update_user_organization( - socket.assigns.user_organization, - user_organization_params - ) do - {:ok, _organization} -> - {:noreply, - socket - |> put_flash(:info, "Board updated successfully") - |> push_navigate(to: socket.assigns.return_to)} + # defp save_user_organization(socket, :edit, user_organization_params) do + # case Organizations.update_user_organization( + # socket.assigns.user_organization, + # user_organization_params + # ) do + # {:ok, _organization} -> + # {:noreply, + # socket + # |> put_flash(:info, "Board updated successfully") + # |> push_navigate(to: socket.assigns.return_to)} - {:error, %Ecto.Changeset{} = changeset} -> - {:noreply, assign(socket, :changeset, changeset)} - end - end + # {:error, %Ecto.Changeset{} = changeset} -> + # {:noreply, assign(socket, :changeset, changeset)} + # end + # end + + # defp save_user_organization(socket, :new, user_organization_params) do + # attrs = + # Map.put( + # user_organization_params, + # "organization_id", + # socket.assigns.organization_id + # ) + + # case Organizations.create_user_organization(attrs) do + # {:ok, _organization} -> + # {:noreply, + # socket + # |> put_flash(:info, "Board created successfully") + # |> push_navigate(to: socket.assigns.return_to)} + + # {:error, %Ecto.Changeset{} = changeset} -> + # {:noreply, assign(socket, changeset: changeset)} + # end + # end - defp save_user_organization(socket, :new, user_organization_params) do - attrs = - Map.put( - user_organization_params, - "organization_id", - socket.assigns.user_organization.organization_id - ) + defp create_board(socket, board_params) do + board_params = Map.put(board_params, "organization_id", socket.assigns.organization_id) - case Organizations.create_user_organization(attrs) do - {:ok, _organization} -> + case Board.create_board(board_params) do + {:ok, _board} -> {:noreply, socket |> put_flash(:info, "Board created successfully") diff --git a/lib/atomic_web/live/board_live/form_component.html.heex b/lib/atomic_web/live/board_live/form_component.html.heex index 9537729f4..2bcd516d1 100644 --- a/lib/atomic_web/live/board_live/form_component.html.heex +++ b/lib/atomic_web/live/board_live/form_component.html.heex @@ -1,11 +1,11 @@

<%= @title %>

- <.form :let={f} for={@changeset} id="board-form" phx-target={@myself} phx-submit="save"> - <%= label(f, :user_id) %> - <%= select(f, :user_id, @users) %> - <%= label(f, :title) %> - <%= text_input(f, :title) %> + <.form :let={f} id="board-form" phx-target={@myself} phx-submit="save"> + <%!-- <%= label(f, :user_id) %> --%> + <%!-- <%= select(f, :user_id, @users) %> --%> + <%!-- <%= label(f, :title) %> --%> + <%!-- <%= text_input(f, :title) %> --%> <%= label(f, :year) %> <%= text_input(f, :year, pattern: "\\d{4}/\\d{4}") %> <%= submit("Save", phx_disable_with: "Saving...") %> diff --git a/lib/atomic_web/live/board_live/index.ex b/lib/atomic_web/live/board_live/index.ex index 3e2a2c6b8..8dccb4c3c 100644 --- a/lib/atomic_web/live/board_live/index.ex +++ b/lib/atomic_web/live/board_live/index.ex @@ -2,8 +2,10 @@ defmodule AtomicWeb.BoardLive.Index do use AtomicWeb, :live_view import AtomicWeb.Components.Empty + import AtomicWeb.Components.Avatar import AtomicWeb.Components.Board import AtomicWeb.Components.Button + import AtomicWeb.Components.Icon alias Atomic.Accounts alias Atomic.Board @@ -18,6 +20,7 @@ defmodule AtomicWeb.BoardLive.Index do @impl true def handle_params(%{"organization_id" => organization_id}, _, socket) do current_year = Year.current_year() + boards = Board.list_boards_by_organization_id(organization_id) board = Board.get_organization_board_by_year(current_year, organization_id) board_departments = @@ -38,6 +41,7 @@ defmodule AtomicWeb.BoardLive.Index do |> assign(:has_permissions?, has_permissions?(socket, organization_id)) |> assign(:organization, organization) |> assign(:role, role) + |> assign(:boards, boards) |> assign(:year, current_year)} end @@ -77,6 +81,23 @@ defmodule AtomicWeb.BoardLive.Index do |> assign(:year, year)} end + @impl true + def handle_event("update_year", %{"year" => year}, socket) do + board = Board.get_organization_board_by_year(year, socket.assigns.organization.id) + + board_departments = + case board do + nil -> [] + _ -> Board.get_board_departments_by_board_id(board.id) + end + + {:noreply, + socket + |> assign(:board_departments, board_departments) + |> assign(:empty?, Enum.empty?(board_departments)) + |> assign(:year, year)} + end + @impl true def handle_event("update-sorting", %{"ids" => ids}, socket) do ids = Enum.filter(ids, fn id -> String.length(id) > 0 end) diff --git a/lib/atomic_web/live/board_live/index.html.heex b/lib/atomic_web/live/board_live/index.html.heex index 84226c333..f55414098 100644 --- a/lib/atomic_web/live/board_live/index.html.heex +++ b/lib/atomic_web/live/board_live/index.html.heex @@ -1,38 +1,91 @@ -
-
    -
    -
    - <.icon name={:arrow_small_left} solid class="cursor-pointer mb-2 mr-2 w-8 h-8 text-zinc-400" phx-click="previous-year" phx-value-organization_id={@organization.id} /> -

    +<.page title="Board"> +
    + <%!--
    + <.icon name={:arrow_small_left} solid class="mr-2 mb-2 h-8 w-8 cursor-pointer text-zinc-400" phx-click="previous-year" phx-value-organization_id={@organization.id} /> +

    <%= gettext("Board") %> <%= @year %>

    - <.icon name={:arrow_small_right} solid class="cursor-pointer mb-2 ml-2 w-8 h-8 text-zinc-400" phx-click="next-year" phx-value-organization_id={@organization.id} /> + <.icon name={:arrow_small_right} solid class="mb-2 ml-2 h-8 w-8 cursor-pointer text-zinc-400" phx-click="next-year" phx-value-organization_id={@organization.id} /> +
    --%> + <%= if not @empty? and @has_permissions? do %> + - <%= if not @empty? and @has_permissions? do %> - + <%= if @empty? and @has_permissions? do %> +
    + <.empty_state url={Routes.board_new_path(@socket, :new, @organization)} placeholder="board member" /> +
    + <% else %> + <%= if @has_permissions? do %> +
    +
    + +
    + + <.button navigate={Routes.board_new_path(@socket, :new, @organization)} color={:light} class="border-none"> + <.icon name={:plus} solid class="h-5 w-5 text-zinc-400" /> + +
    +
    +
    + <%= length(@board_departments) %> departments in your organization
    - <% end %> -
    - <%= if @empty? and @has_permissions? do %> -
    - <.empty_state url={Routes.board_new_path(@socket, :new, @organization)} placeholder="board member" /> +
      + <%= for board_department <- @board_departments do %> +
    • +
      +
      + <%= if @role in [:owner, :admin] do %> + + <.icon name={:bars_3} solid class="h-5 w-5 text-zinc-400" /> + + <% end %> +

      <%= board_department.name %>

      +
      +
      +
      +
      + <%= for user_organization <- Board.get_board_department_users(board_department.id, preloads: [:user]) do %> + <.avatar class="mx-auto" color={:light_gray} name={user_organization.user.name} size={:xs} src={Uploaders.ProfilePicture.url({user_organization.user.profile_picture, user_organization.user}, :original)} /> + <% end %> +
      +
      +
      +
      + + <%= length(Board.get_board_department_users(board_department.id, preloads: [:user])) %> members + + <.button navigate={Routes.board_new_path(@socket, :new, @organization)} color={:light}> + + <.icon name={:pencil} /> + + +
    • + <% end %> +
    <% else %> <%= for board_department <- @board_departments do %> -
  • +
  • <%= if @role in [:owner, :admin] do %> - <.icon name={:bars_3} solid class="w-5 h-5 text-zinc-400" /> + <.icon name={:bars_3} solid class="h-5 w-5 text-zinc-400" /> <% end %>

    <%= board_department.name %>

    -
    -
    +
    +
    <%= for user_organization <- Board.get_board_department_users(board_department.id, preloads: [:user]) do %> <.member_bubble user_organization={user_organization} /> @@ -43,5 +96,5 @@
  • <% end %> <% end %> -

-
+ <% end %> + diff --git a/lib/atomic_web/live/board_live/new.ex b/lib/atomic_web/live/board_live/new.ex index bda0bf760..43664b4ba 100644 --- a/lib/atomic_web/live/board_live/new.ex +++ b/lib/atomic_web/live/board_live/new.ex @@ -11,12 +11,14 @@ defmodule AtomicWeb.BoardLive.New do end @impl true - def handle_params(%{"organization_id" => _organization_id}, _, socket) do + def handle_params(%{"organization_id" => organization_id}, _, socket) do {:noreply, socket |> assign(:current_page, :board) |> assign(:page_title, gettext("New Board")) |> assign(:user_organization, %UserOrganization{}) - |> assign(:users, Enum.map(Accounts.list_users(), fn u -> [key: u.email, value: u.id] end))} + |> assign(:organization_id, organization_id)} + + # |> assign(:users, Enum.map(Accounts.list_users(), fn u -> [key: u.email, value: u.id] end))} end end diff --git a/lib/atomic_web/live/board_live/new.html.heex b/lib/atomic_web/live/board_live/new.html.heex index 36b75b43c..98b7eb334 100644 --- a/lib/atomic_web/live/board_live/new.html.heex +++ b/lib/atomic_web/live/board_live/new.html.heex @@ -1 +1,3 @@ -<.live_component module={AtomicWeb.BoardLive.FormComponent} organization_id={@current_organization.id} users={@users} id={:new} title={@page_title} action={@live_action} user_organization={@user_organization} return_to={Routes.board_index_path(@socket, :index, @current_organization.id)} /> +<.page title={@page_title}> + <.live_component module={AtomicWeb.BoardLive.FormComponent} organization_id={@organization_id} id={:new} title={@page_title} action={@live_action} return_to={Routes.board_index_path(@socket, :index, @current_organization.id)} /> + From 1e8da833d2f68697f08fca0094de291232eccc22 Mon Sep 17 00:00:00 2001 From: Nuno Miguel Date: Fri, 22 Mar 2024 14:10:45 +0000 Subject: [PATCH 2/6] feat: action confirm modal --- lib/atomic_web/components/modal.ex | 4 +- .../live/board_live/form_component.ex | 264 +++++++++++++++--- .../live/board_live/form_component.html.heex | 13 - lib/atomic_web/live/board_live/index.ex | 40 ++- .../live/board_live/index.html.heex | 122 ++++---- lib/atomic_web/router.ex | 9 +- 6 files changed, 325 insertions(+), 127 deletions(-) delete mode 100644 lib/atomic_web/live/board_live/form_component.html.heex diff --git a/lib/atomic_web/components/modal.ex b/lib/atomic_web/components/modal.ex index 1ccc2d21e..1d0dcdc6f 100644 --- a/lib/atomic_web/components/modal.ex +++ b/lib/atomic_web/components/modal.ex @@ -36,7 +36,7 @@ defmodule AtomicWeb.Components.Modal do ~H"""