From 24c672bd6f5b59102e1d47256066f998c42ab8a8 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 13:41:32 +1300 Subject: [PATCH 01/29] feat: Add migrate for signers --- .../20251014001804_create_signers.exs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 priv/repo/migrations/20251014001804_create_signers.exs diff --git a/priv/repo/migrations/20251014001804_create_signers.exs b/priv/repo/migrations/20251014001804_create_signers.exs new file mode 100644 index 0000000..2281f1b --- /dev/null +++ b/priv/repo/migrations/20251014001804_create_signers.exs @@ -0,0 +1,22 @@ +defmodule Foedus.Repo.Migrations.CreateSigners do + use Ecto.Migration + + def change do + create table(:signers, primary_key: false) do + add :id, :binary_id, primary_key: true + add :name, :string, null: false + add :lastname, :string, null: false + add :email, :string, null: false + add :document, :string + add :role, :string + add :birthdate, :date + add :status, :boolean, default: false, null: false + add :company_id, references(:companies, on_delete: :nothing, type: :binary_id), null: false + + timestamps(type: :utc_datetime) + end + + create index(:signers, [:company_id]) + create unique_index(:signers, [:email, :company_id]) + end +end From 5dc0683f763f9881b88440fdc5db9ae94084b069 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 13:41:52 +1300 Subject: [PATCH 02/29] feat: Add schema for signers --- lib/foedus/contracts/signer.ex | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 lib/foedus/contracts/signer.ex diff --git a/lib/foedus/contracts/signer.ex b/lib/foedus/contracts/signer.ex new file mode 100644 index 0000000..b795804 --- /dev/null +++ b/lib/foedus/contracts/signer.ex @@ -0,0 +1,33 @@ +defmodule Foedus.Contracts.Signer do + use Ecto.Schema + import Ecto.Changeset + + @fields_required ~w(name lastname email role company_id)a + @fields_optional ~w(document birthdate status)a + + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + schema "signers" do + field :name, :string + field :status, :boolean, default: false + field :role, :string + field :lastname, :string + field :email, :string + field :document, :string + field :birthdate, :date + field :company_id, :binary_id + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(signer, attrs) do + signer + |> cast(attrs, @fields_required ++ @fields_optional) + |> validate_required([@fields_required]) + |> validate_length(:name, min: 2, max: 100) + |> validate_length(:lastname, min: 2, max: 100) + |> validate_format(:email, ~r/^[^\s]+@[^\s]+\.[^\s]+$/, message: "must be a valid email") + |> foreign_key_constraint(:company_id) + end +end From 14357ebda4c5757f92d2ea957b170f36296f346e Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 13:42:26 +1300 Subject: [PATCH 03/29] feat: Update context contracts with signers functions --- lib/foedus/contracts.ex | 96 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/lib/foedus/contracts.ex b/lib/foedus/contracts.ex index a35cfba..7878e66 100644 --- a/lib/foedus/contracts.ex +++ b/lib/foedus/contracts.ex @@ -101,4 +101,100 @@ defmodule Foedus.Contracts do def change_contract_template(%ContractTemplate{} = contract_template, attrs \\ %{}) do ContractTemplate.changeset(contract_template, attrs) end + + alias Foedus.Contracts.Signer + + @doc """ + Returns the list of signers. + + ## Examples + + iex> list_signers() + [%Signer{}, ...] + + """ + def list_signers do + Repo.all(Signer) + end + + @doc """ + Gets a single signer. + + Raises `Ecto.NoResultsError` if the Signer does not exist. + + ## Examples + + iex> get_signer!(123) + %Signer{} + + iex> get_signer!(456) + ** (Ecto.NoResultsError) + + """ + def get_signer!(id), do: Repo.get!(Signer, id) + + @doc """ + Creates a signer. + + ## Examples + + iex> create_signer(%{field: value}) + {:ok, %Signer{}} + + iex> create_signer(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_signer(attrs \\ %{}) do + %Signer{} + |> Signer.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a signer. + + ## Examples + + iex> update_signer(signer, %{field: new_value}) + {:ok, %Signer{}} + + iex> update_signer(signer, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_signer(%Signer{} = signer, attrs) do + signer + |> Signer.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a signer. + + ## Examples + + iex> delete_signer(signer) + {:ok, %Signer{}} + + iex> delete_signer(signer) + {:error, %Ecto.Changeset{}} + + """ + def delete_signer(%Signer{} = signer) do + Repo.delete(signer) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking signer changes. + + ## Examples + + iex> change_signer(signer) + %Ecto.Changeset{data: %Signer{}} + + """ + def change_signer(%Signer{} = signer, attrs \\ %{}) do + Signer.changeset(signer, attrs) + end end From 212695a99ea8c576cab77d24b078263cc3b72480 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 15:01:11 +1300 Subject: [PATCH 04/29] test: Add tests for signers --- test/foedus/contracts_test.exs | 66 +++++++++++++++++++++ test/support/fixtures/contracts_fixtures.ex | 20 +++++++ 2 files changed, 86 insertions(+) diff --git a/test/foedus/contracts_test.exs b/test/foedus/contracts_test.exs index 12d6afa..ca8fcf4 100644 --- a/test/foedus/contracts_test.exs +++ b/test/foedus/contracts_test.exs @@ -67,4 +67,70 @@ defmodule Foedus.ContractsTest do assert %Ecto.Changeset{} = Contracts.change_contract_template(contract_template) end end + + describe "signers" do + alias Foedus.Contracts.Signer + + import Foedus.ContractsFixtures + + @invalid_attrs %{name: nil, status: nil, role: nil, lastname: nil, email: nil, document: nil, birthdate: nil} + + test "list_signers/0 returns all signers" do + signer = signer_fixture() + assert Contracts.list_signers() == [signer] + end + + test "get_signer!/1 returns the signer with given id" do + signer = signer_fixture() + assert Contracts.get_signer!(signer.id) == signer + end + + test "create_signer/1 with valid data creates a signer" do + valid_attrs = %{name: "some name", status: true, role: "some role", lastname: "some lastname", email: "some email", document: "some document", birthdate: ~D[2025-10-13]} + + assert {:ok, %Signer{} = signer} = Contracts.create_signer(valid_attrs) + assert signer.name == "some name" + assert signer.status == true + assert signer.role == "some role" + assert signer.lastname == "some lastname" + assert signer.email == "some email" + assert signer.document == "some document" + assert signer.birthdate == ~D[2025-10-13] + end + + test "create_signer/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Contracts.create_signer(@invalid_attrs) + end + + test "update_signer/2 with valid data updates the signer" do + signer = signer_fixture() + update_attrs = %{name: "some updated name", status: false, role: "some updated role", lastname: "some updated lastname", email: "some updated email", document: "some updated document", birthdate: ~D[2025-10-14]} + + assert {:ok, %Signer{} = signer} = Contracts.update_signer(signer, update_attrs) + assert signer.name == "some updated name" + assert signer.status == false + assert signer.role == "some updated role" + assert signer.lastname == "some updated lastname" + assert signer.email == "some updated email" + assert signer.document == "some updated document" + assert signer.birthdate == ~D[2025-10-14] + end + + test "update_signer/2 with invalid data returns error changeset" do + signer = signer_fixture() + assert {:error, %Ecto.Changeset{}} = Contracts.update_signer(signer, @invalid_attrs) + assert signer == Contracts.get_signer!(signer.id) + end + + test "delete_signer/1 deletes the signer" do + signer = signer_fixture() + assert {:ok, %Signer{}} = Contracts.delete_signer(signer) + assert_raise Ecto.NoResultsError, fn -> Contracts.get_signer!(signer.id) end + end + + test "change_signer/1 returns a signer changeset" do + signer = signer_fixture() + assert %Ecto.Changeset{} = Contracts.change_signer(signer) + end + end end diff --git a/test/support/fixtures/contracts_fixtures.ex b/test/support/fixtures/contracts_fixtures.ex index 3a19484..dcf5122 100644 --- a/test/support/fixtures/contracts_fixtures.ex +++ b/test/support/fixtures/contracts_fixtures.ex @@ -18,4 +18,24 @@ defmodule Foedus.ContractsFixtures do contract_template end + + @doc """ + Generate a signer. + """ + def signer_fixture(attrs \\ %{}) do + {:ok, signer} = + attrs + |> Enum.into(%{ + birthdate: ~D[2025-10-13], + document: "some document", + email: "some email", + lastname: "some lastname", + name: "some name", + role: "some role", + status: true + }) + |> Foedus.Contracts.create_signer() + + signer + end end From 22f497fa423421c809122849059e09dacc5a8001 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 15:08:05 +1300 Subject: [PATCH 05/29] feat: Update schema with relationship company --- lib/foedus/contracts/signer.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/foedus/contracts/signer.ex b/lib/foedus/contracts/signer.ex index b795804..da33e2b 100644 --- a/lib/foedus/contracts/signer.ex +++ b/lib/foedus/contracts/signer.ex @@ -2,6 +2,8 @@ defmodule Foedus.Contracts.Signer do use Ecto.Schema import Ecto.Changeset + alias Foedus.Accounts.Company + @fields_required ~w(name lastname email role company_id)a @fields_optional ~w(document birthdate status)a @@ -15,7 +17,8 @@ defmodule Foedus.Contracts.Signer do field :email, :string field :document, :string field :birthdate, :date - field :company_id, :binary_id + + belongs_to :company, Company timestamps(type: :utc_datetime) end From 4da4e869392d772381e1e04fa74de8aeabca0942 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 15:08:20 +1300 Subject: [PATCH 06/29] feat: Add factory signer --- test/support/factories/signer_factory.ex | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/support/factories/signer_factory.ex diff --git a/test/support/factories/signer_factory.ex b/test/support/factories/signer_factory.ex new file mode 100644 index 0000000..94106a3 --- /dev/null +++ b/test/support/factories/signer_factory.ex @@ -0,0 +1,20 @@ +defmodule Foedus.SignerFactory do + alias Foedus.Contracts.Signer + + defmacro __using__(_opts) do + quote do + def signer_factory do + %Signer{ + role: Enum.random([:legal, :autorizado]), + name: Faker.Person.first_name(), + status: Enum.random([true, false]), + lastname: Faker.Person.last_name(), + document: Faker.format("###.###.###-##"), + birthdate: Faker.Date.date_of_birth(18..80), + email: Faker.Internet.email(), + company: build(:company) + } + end + end + end +end From f5541db137605503c66cce78953087c26e053b13 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 15:08:35 +1300 Subject: [PATCH 07/29] feat: Add factory signer in factory root --- test/support/factory.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/test/support/factory.ex b/test/support/factory.ex index c6e1558..44d0f45 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -8,4 +8,5 @@ defmodule Foedus.Factory do use Foedus.AddressFactory use Foedus.CompanyFactory use Foedus.PlatformAccessFactory + use Foedus.SignerFactory end From 4ca19135395bffc8a80ba5cea3eac1cf4eca12eb Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 20:24:03 +1300 Subject: [PATCH 08/29] feat: Update links navbar --- lib/foedus_web/components/layouts.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/foedus_web/components/layouts.ex b/lib/foedus_web/components/layouts.ex index 618b124..249a6bf 100644 --- a/lib/foedus_web/components/layouts.ex +++ b/lib/foedus_web/components/layouts.ex @@ -57,7 +57,7 @@ defmodule FoedusWeb.Layouts do <.nav_link navigate={~p"/dashboard"}>Dashboard <.nav_link navigate={~p"/contract_templates"}>Contract Templates <.nav_link navigate={~p"/contractors"}>Contractors - <.nav_link href="#">Relatórios + <.nav_link navigate={~p"/signers"}>Signers <% end %> """ @@ -108,8 +108,8 @@ defmodule FoedusWeb.Layouts do
<.mobile_nav_link navigate={~p"/dashboard"}>Dashboard <.mobile_nav_link navigate={~p"/contract_templates"}>Templates - <.mobile_nav_link href="#">Contratos - <.mobile_nav_link href="#">Relatórios + <.mobile_nav_link navigate={~p"/contractors"}>Contractors + <.mobile_nav_link navigate={~p"/signers"}>Signers
From 7584eba7795cd109073dfca81832e3f470b8b924 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 20:26:29 +1300 Subject: [PATCH 09/29] chore: Adjustment signer factory field role --- test/support/factories/signer_factory.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/support/factories/signer_factory.ex b/test/support/factories/signer_factory.ex index 94106a3..134e4ac 100644 --- a/test/support/factories/signer_factory.ex +++ b/test/support/factories/signer_factory.ex @@ -5,7 +5,7 @@ defmodule Foedus.SignerFactory do quote do def signer_factory do %Signer{ - role: Enum.random([:legal, :autorizado]), + role: Enum.random(["witness", "contractee"]), name: Faker.Person.first_name(), status: Enum.random([true, false]), lastname: Faker.Person.last_name(), From b2f6bdf780d63cfb220ee2d802707f99ffe04e95 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Tue, 14 Oct 2025 20:35:58 +1300 Subject: [PATCH 10/29] feat: Add signers listing --- lib/foedus_web/live/signer_live/index.ex | 30 +++++++++++++++++ .../live/signer_live/index.html.heex | 32 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 lib/foedus_web/live/signer_live/index.ex create mode 100644 lib/foedus_web/live/signer_live/index.html.heex diff --git a/lib/foedus_web/live/signer_live/index.ex b/lib/foedus_web/live/signer_live/index.ex new file mode 100644 index 0000000..aabb915 --- /dev/null +++ b/lib/foedus_web/live/signer_live/index.ex @@ -0,0 +1,30 @@ +defmodule FoedusWeb.SignerLive.Index do + use FoedusWeb, :live_view + + import FoedusWeb.Components.UI.Table + + alias Foedus.Contracts + alias Foedus.Contracts.Signer + + def mount(_params, _session, socket) do + signers = Contracts.list_signers() + changeset = Contracts.change_signer(%Signer{}, %{}) + + socket = + socket + |> assign(:form, to_form(changeset)) + |> stream(:signers, signers) + + {:ok, assign(socket, :page_title, "Signers")} + end + + def handle_params(params, _url, socket) do + {:noreply, apply_action(socket, socket.assigns.live_action, params)} + end + + defp apply_action(socket, :index, _params) do + socket + |> assign(:page_title, "Listing Signers") + |> assign(:signer, nil) + end +end diff --git a/lib/foedus_web/live/signer_live/index.html.heex b/lib/foedus_web/live/signer_live/index.html.heex new file mode 100644 index 0000000..0b91810 --- /dev/null +++ b/lib/foedus_web/live/signer_live/index.html.heex @@ -0,0 +1,32 @@ +
+
+ <.header class="mb-8"> +

Signers

+ <:subtitle> +

Manage your signers here.

+ + + + <.data_table + id="signers-table" + rows={@streams.signers} + columns={[ + %{field: :id, label: "ID"}, + %{field: :name, label: "Name"}, + %{field: :role, label: "Role"}, + %{field: :status, label: "Status"}, + %{field: :inserted_at, label: "Created at", formatter: :datetime} + ]} + actions={[:show, :edit, :delete]} + resource_path="/signers" + table_class="w-full border border-gray-300 rounded-lg shadow-sm overflow-hidden" + thead_class="bg-gradient-to-r from-indigo-500 to-indigo-600" + th_class="px-6 py-4 text-left text-sm font-semibold text-white uppercase tracking-wider" + tbody_class="bg-white divide-y divide-gray-200" + td_class="px-6 py-4 whitespace-nowrap text-sm text-gray-900" + striped={true} + hoverable={true} + empty_message="No signers found. Create your first signers!" + /> +
+
From b4d8eaa562fcb335f1f39920807d72663dc263d3 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Wed, 15 Oct 2025 12:58:54 +1300 Subject: [PATCH 11/29] feat: Add SignerDetails component --- .../components/ui/signer_details.ex | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 lib/foedus_web/components/ui/signer_details.ex diff --git a/lib/foedus_web/components/ui/signer_details.ex b/lib/foedus_web/components/ui/signer_details.ex new file mode 100644 index 0000000..dafaaf8 --- /dev/null +++ b/lib/foedus_web/components/ui/signer_details.ex @@ -0,0 +1,129 @@ +defmodule FoedusWeb.Components.UI.SignerDetails do + use Phoenix.Component + import FoedusWeb.Components.UI.Icon + + attr :signer, :map, required: true + + def signer_details(assigns) do + ~H""" +
+
+
+ <.detail_field label="First Name" value={@signer.name} /> + <.detail_field label="Last Name" value={@signer.lastname} /> + +
+ +
+ <.icon name="hero-envelope" class="w-4 h-4 text-gray-400" /> + + {@signer.email} + +
+
+ + <.detail_field_with_icon + label="Birth Date" + icon="hero-cake" + value={format_date(@signer.birthdate)} + /> + + <.detail_field_with_icon + label="Document" + icon="hero-identification" + value={format_document(@signer.document)} + /> + +
+ + + {@signer.role} + +
+ +
+ + + {status_label(@signer.status)} + +
+
+
+
+ """ + end + + attr :label, :string, required: true + attr :value, :string, required: true + + defp detail_field(assigns) do + ~H""" +
+ +

{@value}

+
+ """ + end + + attr :label, :string, required: true + attr :icon, :string, required: true + attr :value, :string, required: true + + defp detail_field_with_icon(assigns) do + ~H""" +
+ +
+ <.icon name={@icon} class="w-4 h-4 text-gray-400" /> +

{@value}

+
+
+ """ + end + + defp status_label(true), do: "Active" + defp status_label(false), do: "Inactive" + defp status_label(_), do: "Inactive" + + defp status_badge_class(true), do: "bg-green-100 text-green-700" + defp status_badge_class(false), do: "bg-gray-100 text-gray-700" + defp status_badge_class(_), do: "bg-gray-100 text-gray-700" + + defp format_document(document) when is_binary(document) do + document + |> String.replace(~r/[^\d]/, "") + |> case do + <> -> + "#{a}.#{b}.#{c}-#{d}" + + <> -> + "#{a}.#{b}.#{c}/#{d}-#{e}" + + doc -> + doc + end + end + + defp format_document(nil), do: "N/A" + + defp format_date(%Date{} = date), do: Calendar.strftime(date, "%d/%m/%Y") + defp format_date(nil), do: "N/A" + defp format_date(_), do: "N/A" +end From 2c88d30f4e5712e42429c63656f04ee07ea20b97 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Wed, 15 Oct 2025 13:01:05 +1300 Subject: [PATCH 12/29] feat: Add signer details page --- lib/foedus_web/live/signer_live/show.ex | 60 ++++++++++++++ .../live/signer_live/show.html.heex | 83 +++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 lib/foedus_web/live/signer_live/show.ex create mode 100644 lib/foedus_web/live/signer_live/show.html.heex diff --git a/lib/foedus_web/live/signer_live/show.ex b/lib/foedus_web/live/signer_live/show.ex new file mode 100644 index 0000000..adae2b1 --- /dev/null +++ b/lib/foedus_web/live/signer_live/show.ex @@ -0,0 +1,60 @@ +defmodule FoedusWeb.SignerLive.Show do + use FoedusWeb, :live_view + + import FoedusWeb.Components.UI.{ + Breadcrumb, + MetadataCard, + Card, + ShowHeader, + SignerDetails, + Icon + } + + alias Foedus.Contracts + + @impl true + def mount(_params, _session, socket) do + {:ok, socket} + end + + @impl true + def handle_params(%{"id" => id}, _, socket) do + Contracts.get_signer!(id) |> IO.inspect(label: "signer") + + {:noreply, + socket + |> assign(:page_title, page_title(socket.assigns.live_action)) + |> assign(:signer, Contracts.get_signer!(id))} + end + + defp page_title(:show), do: "Show Signer" + defp page_title(:edit), do: "Edit Signer" + + defp full_name(signer) do + first = signer.name || "" + last = signer.lastname || "" + name = String.trim("#{first} #{last}") + if name == "", do: "Signer ##{signer.id}", else: name + end + + defp format_document(document) when is_binary(document) do + document + |> String.replace(~r/[^\d]/, "") + |> case do + <> -> + "#{a}.#{b}.#{c}-#{d}" + + <> -> + "#{a}.#{b}.#{c}/#{d}-#{e}" + + doc -> + doc + end + end + + defp format_document(nil), do: "N/A" + + defp format_date(%Date{} = date), do: Calendar.strftime(date, "%d/%m/%Y") + defp format_date(nil), do: "N/A" + defp format_date(_), do: "N/A" +end diff --git a/lib/foedus_web/live/signer_live/show.html.heex b/lib/foedus_web/live/signer_live/show.html.heex new file mode 100644 index 0000000..4f6d5dc --- /dev/null +++ b/lib/foedus_web/live/signer_live/show.html.heex @@ -0,0 +1,83 @@ +
+
+ <.breadcrumb> + <.breadcrumb_link navigate={~p"/signers"}> + Signers + + <.breadcrumb_separator /> + <.breadcrumb_current> + {full_name(@signer)} + + + + <.show_header + title={full_name(@signer)} + subtitle={@signer.email} + description="Signer details and management" + icon="hero-user-circle" + back_url={~p"/signers"} + edit_url={~p"/signers/#{@signer.id}/edit"} + /> + +
+
+ <.card title="Signer Information" icon="hero-user" icon_color="blue" class="w-full"> + <.signer_details signer={@signer} /> + + + <.card title="Activity" icon="hero-clock" icon_color="green" class="w-full"> +
+
+
+ <.icon name="hero-document-text" class="w-12 h-12 mx-auto mb-3 text-gray-300" /> +

No recent activity

+
+
+
+ +
+ +
+ <.card title="Quick Actions" icon="hero-bolt" icon_color="yellow" class="w-full"> +
+ + {if @signer.status, do: "Deactivate Signer", else: "Activate Signer"} + +
+ + + <.card title="Metadata" icon="hero-information-circle" icon_color="purple" class="w-full"> +
+
+ <.metadata_card> + <.date_info_item + icon="plus" + title="Created" + date={@signer.inserted_at} + color="green" + /> + <.date_info_item + icon="refresh" + title="Last Updated" + date={@signer.updated_at} + color="blue" + /> + +
+
+ +
+
+
+
From a36e819c56ed96ffe1d8e25624eeaf51592a4c5f Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Wed, 15 Oct 2025 13:01:28 +1300 Subject: [PATCH 13/29] chore: Update return mount --- lib/foedus_web/live/signer_live/index.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/foedus_web/live/signer_live/index.ex b/lib/foedus_web/live/signer_live/index.ex index aabb915..b68f474 100644 --- a/lib/foedus_web/live/signer_live/index.ex +++ b/lib/foedus_web/live/signer_live/index.ex @@ -15,7 +15,7 @@ defmodule FoedusWeb.SignerLive.Index do |> assign(:form, to_form(changeset)) |> stream(:signers, signers) - {:ok, assign(socket, :page_title, "Signers")} + {:ok, socket} end def handle_params(params, _url, socket) do From f6bb8fab2271038b89fa970e5c6e3a98f959e87a Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sun, 19 Oct 2025 00:30:16 +1300 Subject: [PATCH 14/29] feat: Add form new signer --- lib/foedus_web/live/signer_live/new.ex | 40 ++++++ lib/foedus_web/live/signer_live/new.html.heex | 132 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 lib/foedus_web/live/signer_live/new.ex create mode 100644 lib/foedus_web/live/signer_live/new.html.heex diff --git a/lib/foedus_web/live/signer_live/new.ex b/lib/foedus_web/live/signer_live/new.ex new file mode 100644 index 0000000..bab09b6 --- /dev/null +++ b/lib/foedus_web/live/signer_live/new.ex @@ -0,0 +1,40 @@ +defmodule FoedusWeb.SignerLive.New do + use FoedusWeb, :live_component + + import FoedusWeb.Components.UI.{ + FormBuilder, + FieldInput + } + + alias Foedus.Contracts + + @impl true + def mount(socket) do + {:ok, socket} + end + + @impl true + def update(%{signer: signer} = assigns, socket) do + signer = Map.put(signer, :status, signer.status || true) + + changeset = Contracts.change_signer(signer) + + {:ok, + socket + |> assign(assigns) + |> assign(:signer, signer) + |> assign(:form, to_form(changeset))} + end + + @impl true + def handle_event("validate", %{"signer" => signer_params}, socket) do + changeset = + socket.assigns.signer + |> Contracts.change_signer(signer_params) + |> Map.put(:action, :validate) + + {:noreply, assign(socket, :form, to_form(changeset))} + end + + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) +end diff --git a/lib/foedus_web/live/signer_live/new.html.heex b/lib/foedus_web/live/signer_live/new.html.heex new file mode 100644 index 0000000..ae31aa4 --- /dev/null +++ b/lib/foedus_web/live/signer_live/new.html.heex @@ -0,0 +1,132 @@ +
+
+
+
+ + + +
+
+

{@title}

+

+ Add signers to your contracts and manage their information +

+
+
+ + +
+ +
+ <.form_builder + for={@form} + id="signer-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + class="space-y-6" + > +
+ <.field_input + field={@form[:name]} + label="First Name" + required + /> + <.field_input + field={@form[:lastname]} + label="Last Name" + required + /> + <.field_input + field={@form[:email]} + type="email" + label="Email Address" + required + /> +
+ +
+ <.field_input + field={@form[:document]} + label="Document Number" + required + /> + <.field_input + field={@form[:birthdate]} + type="date" + label="Birth Date" + required + /> + <.field_input + field={@form[:role]} + type="select" + label="Role" + prompt="Select a role" + options={[ + {"Director", "director"}, + {"Manager", "manager"}, + {"Coordinator", "coordinator"} + ]} + required + /> +
+ + <.field_input + field={@form[:status]} + type="checkbox" + label="Active" + /> + +
+ + Cancel + + + + + + + {if @action == :new, do: "Create Signer", else: "Update Signer"} + +
+ +
+
From b78be9a94fd7904999a1281128c740e95a757417 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sun, 19 Oct 2025 00:30:37 +1300 Subject: [PATCH 15/29] feat: Add routers for signer --- lib/foedus_web/router.ex | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/foedus_web/router.ex b/lib/foedus_web/router.ex index 399d68f..b4ba86e 100644 --- a/lib/foedus_web/router.ex +++ b/lib/foedus_web/router.ex @@ -66,6 +66,11 @@ defmodule FoedusWeb.Router do live "/contract_templates/new", ContractTemplateLive.Index, :new live "/contract_templates/:id/edit", ContractTemplateLive.Index, :edit + live "/signers", SignerLive.Index, :index + live "/signers/new", SignerLive.Index, :new + live "/signers/:id/edit", SignerLive.Index, :edit + live "/signers/:id", SignerLive.Show, :show + live "/contract_templates/:id", ContractTemplateLive.Show, :show end end From 5aefb4ee9ea40ff1ba0ca0510411b436a7b014c3 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sun, 19 Oct 2025 00:31:15 +1300 Subject: [PATCH 16/29] feat: Add component form_builder and field_input --- lib/foedus_web/components/ui/field_input.ex | 105 +++++++++++++++++++ lib/foedus_web/components/ui/form_builder.ex | 26 +++++ 2 files changed, 131 insertions(+) create mode 100644 lib/foedus_web/components/ui/field_input.ex create mode 100644 lib/foedus_web/components/ui/form_builder.ex diff --git a/lib/foedus_web/components/ui/field_input.ex b/lib/foedus_web/components/ui/field_input.ex new file mode 100644 index 0000000..61df156 --- /dev/null +++ b/lib/foedus_web/components/ui/field_input.ex @@ -0,0 +1,105 @@ +defmodule FoedusWeb.Components.UI.FieldInput do + use Phoenix.Component + + attr :field, :any, required: true + attr :type, :string, default: "text" + attr :label, :string, required: true + attr :placeholder, :string, default: nil + attr :required, :boolean, default: false + attr :options, :list, default: [] + attr :prompt, :string, default: nil + attr :class, :string, default: "" + + def field_input(%{type: "select"} = assigns) do + ~H""" +
+ + +

+ {translate_errors(@field.errors)} +

+
+ """ + end + + def field_input(%{type: "checkbox"} = assigns) do + ~H""" +
+ + +
+ """ + end + + def field_input(%{type: "textarea"} = assigns) do + ~H""" +
+ + +

+ {translate_errors(@field.errors)} +

+
+ """ + end + + def field_input(assigns) do + ~H""" +
+ + +

+ {translate_errors(@field.errors)} +

+
+ """ + end + + defp translate_errors(errors) when is_list(errors) do + Enum.map_join(errors, ", ", fn {msg, _opts} -> msg end) + end +end diff --git a/lib/foedus_web/components/ui/form_builder.ex b/lib/foedus_web/components/ui/form_builder.ex new file mode 100644 index 0000000..da9d30c --- /dev/null +++ b/lib/foedus_web/components/ui/form_builder.ex @@ -0,0 +1,26 @@ +defmodule FoedusWeb.Components.UI.FormBuilder do + use Phoenix.Component + + attr :for, :any, required: true + attr :id, :string, required: true + attr :action, :string, default: nil + attr :class, :string, default: "" + attr :rest, :global, include: ~w(phx-change phx-submit phx-target) + slot :inner_block, required: true + + def form_builder(assigns) do + ~H""" +
+ <.form + for={@for} + id={@id} + action={@action} + class={["space-y-8", @class]} + {@rest} + > + {render_slot(@inner_block)} + +
+ """ + end +end From 74b80e1c28a654b1b40fe0cfec5d7051e8dd1b2b Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sun, 19 Oct 2025 00:31:46 +1300 Subject: [PATCH 17/29] feat: Add signer modal --- lib/foedus_web/live/signer_live/index.ex | 8 +++++ .../live/signer_live/index.html.heex | 35 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/foedus_web/live/signer_live/index.ex b/lib/foedus_web/live/signer_live/index.ex index b68f474..e291efb 100644 --- a/lib/foedus_web/live/signer_live/index.ex +++ b/lib/foedus_web/live/signer_live/index.ex @@ -22,6 +22,14 @@ defmodule FoedusWeb.SignerLive.Index do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end + defp apply_action(socket, :new, _params) do + socket + |> assign(:page_title, "New Signer") + |> assign(:signer, %Signer{}) + end + + + defp apply_action(socket, :index, _params) do socket |> assign(:page_title, "Listing Signers") diff --git a/lib/foedus_web/live/signer_live/index.html.heex b/lib/foedus_web/live/signer_live/index.html.heex index 0b91810..e08c3b5 100644 --- a/lib/foedus_web/live/signer_live/index.html.heex +++ b/lib/foedus_web/live/signer_live/index.html.heex @@ -1,10 +1,22 @@
<.header class="mb-8"> -

Signers

+

Signer

<:subtitle> -

Manage your signers here.

+

+ Manage your signers here. +

+ <:actions> + <.link + patch={~p"/signers/new"} + phx-click={JS.push_focus()} + > + <.button class="bg-indigo-700 hover:bg-indigo-500 text-white font-medium px-6 py-3 rounded-lg shadow-sm transition-colors duration-200"> + New Signer + + + <.data_table @@ -28,5 +40,24 @@ hoverable={true} empty_message="No signers found. Create your first signers!" /> + + <.modal + :if={@live_action in [:new, :edit]} + id="signer-modal" + show + on_cancel={JS.patch(~p"/signers")} + class="!w-[98vw] !h-[95vh] !m-2" + > +
+ <.live_component + module={FoedusWeb.SignerLive.New} + id={@signer.id || :new} + title={@page_title} + action={@live_action} + signer={@signer} + patch={~p"/signers"} + /> +
+
From 3d95c641f2f184ebebbbf37a76d1be677a173d71 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sun, 19 Oct 2025 01:45:37 +1300 Subject: [PATCH 18/29] feat: Add component form_actions and form_header --- lib/foedus_web/components/ui/form_actions.ex | 50 ++++++++++++++++ lib/foedus_web/components/ui/form_header.ex | 61 ++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 lib/foedus_web/components/ui/form_actions.ex create mode 100644 lib/foedus_web/components/ui/form_header.ex diff --git a/lib/foedus_web/components/ui/form_actions.ex b/lib/foedus_web/components/ui/form_actions.ex new file mode 100644 index 0000000..100b712 --- /dev/null +++ b/lib/foedus_web/components/ui/form_actions.ex @@ -0,0 +1,50 @@ +defmodule FoedusWeb.Components.UI.FormActions do + use Phoenix.Component + + import FoedusWeb.Components.UI.Button + + @doc """ + Renders form action buttons (Cancel and Submit). + + ## Examples + + <.form_actions + on_cancel={JS.patch(~p"/signers")} + submit_text="Create Signer" + /> + """ + attr :on_cancel, Phoenix.LiveView.JS, required: true + attr :submit_text, :string, required: true + attr :show_icon, :boolean, default: true + + def form_actions(assigns) do + ~H""" +
+ <.button + variant="secondary" + size="lg" + type="button" + phx-click={@on_cancel} + > + Cancel + + + <.button + variant="cta_primary" + size="lg" + type="submit" + > + + + + {@submit_text} + +
+ """ + end +end diff --git a/lib/foedus_web/components/ui/form_header.ex b/lib/foedus_web/components/ui/form_header.ex new file mode 100644 index 0000000..6f8dbcb --- /dev/null +++ b/lib/foedus_web/components/ui/form_header.ex @@ -0,0 +1,61 @@ +defmodule FoedusWeb.Components.UI.FormHeader do + use Phoenix.Component + alias FoedusWeb.Components.UI.Icon + + @doc """ + Renders a gradient header with icon, title, subtitle and close button for forms. + + ## Examples + + <.form_header + title="New Signer" + subtitle="Add signers to your contracts" + icon="user" + on_close={JS.patch(~p"/signers")} + /> + """ + attr :title, :string, required: true + attr :subtitle, :string, default: nil + attr :icon, :string, required: true + attr :on_close, Phoenix.LiveView.JS, required: true + attr :gradient, :string, default: "from-indigo-600 to-purple-600" + + def form_header(assigns) do + ~H""" +
+
+
+ +
+
+

{@title}

+

+ {@subtitle} +

+
+
+ + +
+ """ + end +end From 4b6e6fcd32da6a587eae9714f99becf06a0db26d Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sun, 19 Oct 2025 01:46:05 +1300 Subject: [PATCH 19/29] feat: Use components form --- lib/foedus_web/live/signer_live/new.ex | 4 +- lib/foedus_web/live/signer_live/new.html.heex | 76 ++----------------- 2 files changed, 11 insertions(+), 69 deletions(-) diff --git a/lib/foedus_web/live/signer_live/new.ex b/lib/foedus_web/live/signer_live/new.ex index bab09b6..d5e4a63 100644 --- a/lib/foedus_web/live/signer_live/new.ex +++ b/lib/foedus_web/live/signer_live/new.ex @@ -3,7 +3,9 @@ defmodule FoedusWeb.SignerLive.New do import FoedusWeb.Components.UI.{ FormBuilder, - FieldInput + FieldInput, + FormActions, + FormHeader } alias Foedus.Contracts diff --git a/lib/foedus_web/live/signer_live/new.html.heex b/lib/foedus_web/live/signer_live/new.html.heex index ae31aa4..2a1ab53 100644 --- a/lib/foedus_web/live/signer_live/new.html.heex +++ b/lib/foedus_web/live/signer_live/new.html.heex @@ -1,46 +1,11 @@
-
-
-
- - - -
-
-

{@title}

-

- Add signers to your contracts and manage their information -

-
-
- - -
- + <.form_header + title="New Signer" + subtitle="Add signers to your contracts" + icon="user" + on_close={JS.patch(~p"/signers")} + /> +
<.form_builder for={@form} @@ -101,32 +66,7 @@ label="Active" /> -
- - Cancel - - - - - - - {if @action == :new, do: "Create Signer", else: "Update Signer"} - -
+ <.form_actions on_cancel={JS.patch(~p"/signers")} submit_text="Create Signer" />
From a63373c7dd56160acc9340116d69d8f8d8ff6904 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 04:40:37 -0300 Subject: [PATCH 20/29] chore: Update module name to form_component --- .../live/signer_live/form_component.ex | 106 ++++++++++++++++++ .../live/signer_live/form_component.html.heex | 43 +++++++ lib/foedus_web/live/signer_live/new.ex | 42 ------- lib/foedus_web/live/signer_live/new.html.heex | 72 ------------ 4 files changed, 149 insertions(+), 114 deletions(-) create mode 100644 lib/foedus_web/live/signer_live/form_component.ex create mode 100644 lib/foedus_web/live/signer_live/form_component.html.heex delete mode 100644 lib/foedus_web/live/signer_live/new.ex delete mode 100644 lib/foedus_web/live/signer_live/new.html.heex diff --git a/lib/foedus_web/live/signer_live/form_component.ex b/lib/foedus_web/live/signer_live/form_component.ex new file mode 100644 index 0000000..76d8993 --- /dev/null +++ b/lib/foedus_web/live/signer_live/form_component.ex @@ -0,0 +1,106 @@ +defmodule FoedusWeb.SignerLive.FormComponent do + use FoedusWeb, :live_component + + import FoedusWeb.Components.UI.{ + FormBuilder, + FieldInput, + FormActions, + FormHeader + } + + alias Foedus.Contracts + alias Foedus.Contracts.Signer + + def mount(_params, _session, socket) do + signers = Contracts.list_signers() + changeset = Contracts.change_signer(%Signer{}, %{}) + + socket = + socket + |> assign(:form, to_form(changeset)) + |> stream(:signers, signers) + + {:ok, socket} + end + + @impl true + def update(%{signer: signer} = assigns, socket) do + signer = Map.put(signer, :status, signer.status || true) + + signer = + Map.update(signer, :status, true, fn + nil -> true + val -> val + end) + + changeset = Contracts.change_signer(signer) + + {:ok, + socket + |> assign(assigns) + |> assign(:signer, signer) + |> assign(:form, to_form(changeset))} + end + + @impl true + def handle_event("validate", %{"signer" => signer_params}, socket) do + signer_params = normalize_status_param(signer_params) + + changeset = + socket.assigns.signer + |> Contracts.change_signer(signer_params) + |> Map.put(:action, :validate) + + {:noreply, assign(socket, :form, to_form(changeset))} + end + + @impl true + def handle_event("save", %{"signer" => signer_params}, socket) do + company_id = socket.assigns.current_user.company_id + signer_params = normalize_status_param(signer_params) + save_signer(socket, socket.assigns.action, signer_params, company_id) + end + + defp normalize_status_param(params) do + case Map.get(params, "status") do + nil -> Map.put(params, "status", false) + "true" -> Map.put(params, "status", true) + val when is_boolean(val) -> params + _ -> Map.put(params, "status", false) + end + end + + defp save_signer(socket, :new, signer_params, company_id) do + signer_params = Map.put(signer_params, "company_id", company_id) + + case Contracts.create_signer(signer_params) do + {:ok, signer} -> + notify_parent({:saved, signer}) + + {:noreply, + socket + |> put_flash(:info, "Signer created successfully") + |> push_patch(to: socket.assigns.patch)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, :form, to_form(changeset))} + end + end + + defp save_signer(socket, :edit, signer_params, _company_id) do + case Contracts.update_signer(socket.assigns.signer, signer_params) do + {:ok, signer} -> + notify_parent({:saved, signer}) + + {:noreply, + socket + |> put_flash(:info, "Signer updated successfully") + |> push_patch(to: socket.assigns.patch)} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, :form, to_form(changeset))} + end + end + + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) +end diff --git a/lib/foedus_web/live/signer_live/form_component.html.heex b/lib/foedus_web/live/signer_live/form_component.html.heex new file mode 100644 index 0000000..edd4a28 --- /dev/null +++ b/lib/foedus_web/live/signer_live/form_component.html.heex @@ -0,0 +1,43 @@ +
+ <.form_header + title="New Signer" + subtitle="Add signers to your contracts" + icon="user" + on_close={JS.patch(~p"/signers")} + /> + +
+ <.form_builder + for={@form} + id="signer-form" + phx-target={@myself} + phx-change="validate" + phx-submit="save" + class="space-y-6" + > +
+ <.field_input field={@form[:name]} label="First Name" required /> + <.field_input field={@form[:lastname]} label="Last Name" required /> + <.field_input field={@form[:email]} type="email" label="Email Address" required /> +
+ +
+ <.field_input field={@form[:document]} label="Document Number" required /> + <.field_input field={@form[:birthdate]} type="date" label="Birth Date" required /> + <.field_input field={@form[:role]} type="select" label="Role" prompt="Select a role" + options={[{"Director", "director"}, {"Manager", "manager"}, {"Coordinator", "coordinator"}]} + required /> +
+ + <.field_input field={@form[:status]} type="checkbox" label="Active" /> + +
+ <.form_actions + on_cancel={JS.patch(~p"/signers")} + submit_text="Create Signer" + show_icon={true} + /> +
+ +
+
\ No newline at end of file diff --git a/lib/foedus_web/live/signer_live/new.ex b/lib/foedus_web/live/signer_live/new.ex deleted file mode 100644 index d5e4a63..0000000 --- a/lib/foedus_web/live/signer_live/new.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule FoedusWeb.SignerLive.New do - use FoedusWeb, :live_component - - import FoedusWeb.Components.UI.{ - FormBuilder, - FieldInput, - FormActions, - FormHeader - } - - alias Foedus.Contracts - - @impl true - def mount(socket) do - {:ok, socket} - end - - @impl true - def update(%{signer: signer} = assigns, socket) do - signer = Map.put(signer, :status, signer.status || true) - - changeset = Contracts.change_signer(signer) - - {:ok, - socket - |> assign(assigns) - |> assign(:signer, signer) - |> assign(:form, to_form(changeset))} - end - - @impl true - def handle_event("validate", %{"signer" => signer_params}, socket) do - changeset = - socket.assigns.signer - |> Contracts.change_signer(signer_params) - |> Map.put(:action, :validate) - - {:noreply, assign(socket, :form, to_form(changeset))} - end - - defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) -end diff --git a/lib/foedus_web/live/signer_live/new.html.heex b/lib/foedus_web/live/signer_live/new.html.heex deleted file mode 100644 index 2a1ab53..0000000 --- a/lib/foedus_web/live/signer_live/new.html.heex +++ /dev/null @@ -1,72 +0,0 @@ -
- <.form_header - title="New Signer" - subtitle="Add signers to your contracts" - icon="user" - on_close={JS.patch(~p"/signers")} - /> - -
- <.form_builder - for={@form} - id="signer-form" - phx-target={@myself} - phx-change="validate" - phx-submit="save" - class="space-y-6" - > -
- <.field_input - field={@form[:name]} - label="First Name" - required - /> - <.field_input - field={@form[:lastname]} - label="Last Name" - required - /> - <.field_input - field={@form[:email]} - type="email" - label="Email Address" - required - /> -
- -
- <.field_input - field={@form[:document]} - label="Document Number" - required - /> - <.field_input - field={@form[:birthdate]} - type="date" - label="Birth Date" - required - /> - <.field_input - field={@form[:role]} - type="select" - label="Role" - prompt="Select a role" - options={[ - {"Director", "director"}, - {"Manager", "manager"}, - {"Coordinator", "coordinator"} - ]} - required - /> -
- - <.field_input - field={@form[:status]} - type="checkbox" - label="Active" - /> - - <.form_actions on_cancel={JS.patch(~p"/signers")} submit_text="Create Signer" /> - -
-
From 4c9890c4c944fefb00d056db1724e94083a77527 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 04:52:56 -0300 Subject: [PATCH 21/29] chore: Remove brachets validate_required --- lib/foedus/contracts/signer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/foedus/contracts/signer.ex b/lib/foedus/contracts/signer.ex index da33e2b..6ec8921 100644 --- a/lib/foedus/contracts/signer.ex +++ b/lib/foedus/contracts/signer.ex @@ -27,7 +27,7 @@ defmodule Foedus.Contracts.Signer do def changeset(signer, attrs) do signer |> cast(attrs, @fields_required ++ @fields_optional) - |> validate_required([@fields_required]) + |> validate_required(@fields_required) |> validate_length(:name, min: 2, max: 100) |> validate_length(:lastname, min: 2, max: 100) |> validate_format(:email, ~r/^[^\s]+@[^\s]+\.[^\s]+$/, message: "must be a valid email") From 0593e2c63dab44542f039b53ec3db3d5e34a95b4 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 04:56:44 -0300 Subject: [PATCH 22/29] feat: Add handle_info to refresh signer table on create, update or delete --- lib/foedus_web/live/signer_live/index.ex | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/foedus_web/live/signer_live/index.ex b/lib/foedus_web/live/signer_live/index.ex index e291efb..e8def8b 100644 --- a/lib/foedus_web/live/signer_live/index.ex +++ b/lib/foedus_web/live/signer_live/index.ex @@ -28,11 +28,24 @@ defmodule FoedusWeb.SignerLive.Index do |> assign(:signer, %Signer{}) end - - defp apply_action(socket, :index, _params) do socket |> assign(:page_title, "Listing Signers") |> assign(:signer, nil) end + + def handle_info({:signer_created, signer}, socket) do + socket = stream_insert(socket, :signer, signer, at: 0) + {:noreply, socket} + end + + def handle_info({:signer_updated, signer}, socket) do + socket = stream_insert(socket, :signer, signer) + {:noreply, socket} + end + + def handle_info({:signer_deleted, signer}, socket) do + socket = stream_delete(socket, :signer, signer) + {:noreply, socket} + end end From 2340c08c0092c267a6a6e8f96ddcb5fa2ce95836 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 05:11:36 -0300 Subject: [PATCH 23/29] feat: Add handle event for delete signer --- lib/foedus_web/live/signer_live/index.ex | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/foedus_web/live/signer_live/index.ex b/lib/foedus_web/live/signer_live/index.ex index e8def8b..46fc25f 100644 --- a/lib/foedus_web/live/signer_live/index.ex +++ b/lib/foedus_web/live/signer_live/index.ex @@ -22,6 +22,14 @@ defmodule FoedusWeb.SignerLive.Index do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end + def handle_event("delete", %{"id" => id}, socket) do + signer = Contracts.get_signer!(id) + {:ok, _} = Contracts.delete_signer(signer) + socket = stream_delete(socket, :signer, signer) + + {:noreply, put_flash(socket, :info, "Signer deleted successfully")} + end + defp apply_action(socket, :new, _params) do socket |> assign(:page_title, "New Signer") From 8037af64f18d1e208d424bca115f83abebcdf4da Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 09:22:13 -0300 Subject: [PATCH 24/29] style: Organize code --- lib/foedus_web/live/signer_live/index.ex | 53 ++++++++++++++---------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/lib/foedus_web/live/signer_live/index.ex b/lib/foedus_web/live/signer_live/index.ex index 46fc25f..0b0838c 100644 --- a/lib/foedus_web/live/signer_live/index.ex +++ b/lib/foedus_web/live/signer_live/index.ex @@ -5,55 +5,62 @@ defmodule FoedusWeb.SignerLive.Index do alias Foedus.Contracts alias Foedus.Contracts.Signer + alias FoedusWeb.SignerLive.FormComponent + @impl true def mount(_params, _session, socket) do signers = Contracts.list_signers() - changeset = Contracts.change_signer(%Signer{}, %{}) - socket = - socket - |> assign(:form, to_form(changeset)) - |> stream(:signers, signers) - - {:ok, socket} + {:ok, stream(socket, :signers, signers)} end + @impl true def handle_params(params, _url, socket) do {:noreply, apply_action(socket, socket.assigns.live_action, params)} end + @impl true def handle_event("delete", %{"id" => id}, socket) do signer = Contracts.get_signer!(id) {:ok, _} = Contracts.delete_signer(signer) - socket = stream_delete(socket, :signer, signer) + socket = stream_delete(socket, :signers, signer) {:noreply, put_flash(socket, :info, "Signer deleted successfully")} end + @impl true + def handle_info({FormComponent, {:saved, signer}}, socket) do + socket = stream_insert(socket, :signers, signer, at: 0) + {:noreply, socket} + end + + @impl true + def handle_info({FormComponent, {:updated, signer}}, socket) do + socket = stream_insert(socket, :signers, signer) + {:noreply, socket} + end + + @impl true + def handle_info({FormComponent, {:deleted, signer}}, socket) do + socket = stream_delete(socket, :signers, signer) + {:noreply, socket} + end + defp apply_action(socket, :new, _params) do socket |> assign(:page_title, "New Signer") |> assign(:signer, %Signer{}) end + defp apply_action(socket, :edit, %{"id" => id}) do + socket + |> assign(:page_title, "Edit Signer") + |> assign(:signer, Contracts.get_signer!(id)) + end + defp apply_action(socket, :index, _params) do socket |> assign(:page_title, "Listing Signers") |> assign(:signer, nil) end - - def handle_info({:signer_created, signer}, socket) do - socket = stream_insert(socket, :signer, signer, at: 0) - {:noreply, socket} - end - - def handle_info({:signer_updated, signer}, socket) do - socket = stream_insert(socket, :signer, signer) - {:noreply, socket} - end - - def handle_info({:signer_deleted, signer}, socket) do - socket = stream_delete(socket, :signer, signer) - {:noreply, socket} - end end From 13a73381fccd2c94fdea472a69e5a175e1ed56a0 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 09:23:31 -0300 Subject: [PATCH 25/29] chore: Set deafult status to true --- lib/foedus/contracts/signer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/foedus/contracts/signer.ex b/lib/foedus/contracts/signer.ex index 6ec8921..38d3a04 100644 --- a/lib/foedus/contracts/signer.ex +++ b/lib/foedus/contracts/signer.ex @@ -11,7 +11,7 @@ defmodule Foedus.Contracts.Signer do @foreign_key_type :binary_id schema "signers" do field :name, :string - field :status, :boolean, default: false + field :status, :boolean, default: true field :role, :string field :lastname, :string field :email, :string From a82a0504b11c56030e088a33c23273f421a90893 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 09:24:15 -0300 Subject: [PATCH 26/29] chore: Update function normalize_status_params --- .../live/signer_live/form_component.ex | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/foedus_web/live/signer_live/form_component.ex b/lib/foedus_web/live/signer_live/form_component.ex index 76d8993..1f30029 100644 --- a/lib/foedus_web/live/signer_live/form_component.ex +++ b/lib/foedus_web/live/signer_live/form_component.ex @@ -13,7 +13,7 @@ defmodule FoedusWeb.SignerLive.FormComponent do def mount(_params, _session, socket) do signers = Contracts.list_signers() - changeset = Contracts.change_signer(%Signer{}, %{}) + changeset = Contracts.change_signer(%Signer{status: true}, %{}) socket = socket @@ -25,14 +25,7 @@ defmodule FoedusWeb.SignerLive.FormComponent do @impl true def update(%{signer: signer} = assigns, socket) do - signer = Map.put(signer, :status, signer.status || true) - - signer = - Map.update(signer, :status, true, fn - nil -> true - val -> val - end) - + signer = if is_nil(signer.status), do: Map.put(signer, :status, true), else: signer changeset = Contracts.change_signer(signer) {:ok, @@ -61,15 +54,6 @@ defmodule FoedusWeb.SignerLive.FormComponent do save_signer(socket, socket.assigns.action, signer_params, company_id) end - defp normalize_status_param(params) do - case Map.get(params, "status") do - nil -> Map.put(params, "status", false) - "true" -> Map.put(params, "status", true) - val when is_boolean(val) -> params - _ -> Map.put(params, "status", false) - end - end - defp save_signer(socket, :new, signer_params, company_id) do signer_params = Map.put(signer_params, "company_id", company_id) @@ -102,5 +86,21 @@ defmodule FoedusWeb.SignerLive.FormComponent do end end + defp normalize_status_param(%{"status" => "true"} = params) do + Map.put(params, "status", true) + end + + defp normalize_status_param(%{"status" => "false"} = params) do + Map.put(params, "status", false) + end + + defp normalize_status_param(%{"status" => val} = params) when is_boolean(val) do + params + end + + defp normalize_status_param(params) do + Map.put(params, "status", false) + end + defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) end From 4bd50830bc5a6618ae766eda6767ea3e36432151 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 09:24:45 -0300 Subject: [PATCH 27/29] chore: Remove div --- lib/foedus_web/components/ui/form_builder.ex | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/foedus_web/components/ui/form_builder.ex b/lib/foedus_web/components/ui/form_builder.ex index da9d30c..47afb84 100644 --- a/lib/foedus_web/components/ui/form_builder.ex +++ b/lib/foedus_web/components/ui/form_builder.ex @@ -10,17 +10,15 @@ defmodule FoedusWeb.Components.UI.FormBuilder do def form_builder(assigns) do ~H""" -
- <.form - for={@for} - id={@id} - action={@action} - class={["space-y-8", @class]} - {@rest} - > - {render_slot(@inner_block)} - -
+ <.form + for={@for} + id={@id} + action={@action} + class={["space-y-8", @class]} + {@rest} + > + {render_slot(@inner_block)} + """ end end From d41af9609826492c120d2ad94a68b2f3d4e443b4 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 09:26:45 -0300 Subject: [PATCH 28/29] refactor: Refactor modulo to form componnt and pass current user to live component --- lib/foedus_web/live/signer_live/index.html.heex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/foedus_web/live/signer_live/index.html.heex b/lib/foedus_web/live/signer_live/index.html.heex index e08c3b5..d598af0 100644 --- a/lib/foedus_web/live/signer_live/index.html.heex +++ b/lib/foedus_web/live/signer_live/index.html.heex @@ -50,8 +50,9 @@ >
<.live_component - module={FoedusWeb.SignerLive.New} + module={FoedusWeb.SignerLive.FormComponent } id={@signer.id || :new} + current_user={@current_user} title={@page_title} action={@live_action} signer={@signer} From 88efe19d46ef9bfb665557d32da60a183ac5fe76 Mon Sep 17 00:00:00 2001 From: brandaoplaster Date: Sat, 25 Oct 2025 09:30:25 -0300 Subject: [PATCH 29/29] style: Remove unused code --- lib/foedus_web/live/signer_live/show.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/foedus_web/live/signer_live/show.ex b/lib/foedus_web/live/signer_live/show.ex index adae2b1..26caf94 100644 --- a/lib/foedus_web/live/signer_live/show.ex +++ b/lib/foedus_web/live/signer_live/show.ex @@ -19,8 +19,6 @@ defmodule FoedusWeb.SignerLive.Show do @impl true def handle_params(%{"id" => id}, _, socket) do - Contracts.get_signer!(id) |> IO.inspect(label: "signer") - {:noreply, socket |> assign(:page_title, page_title(socket.assigns.live_action))