Skip to content

Commit

Permalink
add live views for activity and journal entry (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
jarlah authored Oct 2, 2024
1 parent df69fef commit 6d982e2
Show file tree
Hide file tree
Showing 26 changed files with 834 additions and 19 deletions.
28 changes: 28 additions & 0 deletions lib/journi_plan/itineraries.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ defmodule JourniPlan.Itineraries do
|> Repo.preload([:activities, :journal_entries])
end

def list_user_journal_entries(user_id) do
from(j in JournalEntry, where: j.user_id == ^user_id)
|> Repo.all()
|> Repo.preload([:activity, :itinerary])
end

def get_itinerary!(uuid) do
Repo.get_by!(Itinerary, uuid: uuid)
|> Repo.preload([:activities, :journal_entries])
Expand Down Expand Up @@ -174,6 +180,12 @@ defmodule JourniPlan.Itineraries do
end
end

def list_user_activities(user_id) do
query = from(i in Activity, where: i.user_id == ^user_id)
Repo.all(query)
|> Repo.preload(:itinerary)
end

def list_journal_entries do
Repo.all(JournalEntry)
|> Repo.preload([:activity, :itinerary])
Expand All @@ -184,6 +196,22 @@ defmodule JourniPlan.Itineraries do
|> Repo.preload([:activity, :itinerary])
end

def change_activity(activity, action, params \\ nil)

def change_activity(%Activity{} = activity, :edit, params) do
UpdateActivity.changeset(
%UpdateActivity{uuid: activity.uuid},
params || Map.from_struct(activity)
)
end

def change_activity(_activity, :new, params) do
CreateActivity.changeset(
%CreateActivity{},
params || %{}
)
end

def change_journal_entry(journal_entry, action, params \\ nil)

def change_journal_entry(%JournalEntry{} = journal_entry, :edit, params) do
Expand Down
7 changes: 7 additions & 0 deletions lib/journi_plan/itineraries/aggregates/journal_entry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule JourniPlan.Itineraries.Aggregates.JournalEntry do
:user_id
]

alias JourniPlan.Itineraries.Commands.DeleteJournalEntry
alias JourniPlan.Itineraries.Commands.CreateJournalEntry
alias JourniPlan.Itineraries.Commands.UpdateJournalEntry
alias JourniPlan.Itineraries.Events.JournalEntryCreated
Expand Down Expand Up @@ -42,9 +43,15 @@ defmodule JourniPlan.Itineraries.Aggregates.JournalEntry do
[title_command, body_command] |> Enum.filter(&Function.identity/1)
end


def execute(%JournalEntry{}, %DeleteJournalEntry{uuid: uuid}) do
%JournalEntryDeleted{uuid: uuid}
end

def apply(%JournalEntry{} = journal_entry, %JournalEntryCreated{} = event) do
%JournalEntry{
journal_entry |
uuid: event.uuid,
title: event.title,
body: event.body,
entry_date: event.entry_date,
Expand Down
10 changes: 8 additions & 2 deletions lib/journi_plan/itineraries/projectors/activity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ defmodule JourniPlan.Itineraries.Projectors.Activity do
alias JourniPlan.Itineraries.Projections.Activity

project(%ActivityCreated{} = created, _, fn multi ->
{:ok, start_time, _} = DateTime.from_iso8601(created.start_time)
{:ok, end_time, _} = DateTime.from_iso8601(created.end_time)
start_time =
created.start_time
|> Timex.parse!("%Y-%m-%dT%H:%M", :strftime)
|> Timex.to_datetime("Etc/UTC")
end_time =
created.end_time
|> Timex.parse!("%Y-%m-%dT%H:%M", :strftime)
|> Timex.to_datetime("Etc/UTC")
itinerary_uuid = cast_uuid!(created.itinerary_uuid)

Ecto.Multi.insert(multi, :activity, %Activity{
Expand Down
5 changes: 4 additions & 1 deletion lib/journi_plan/itineraries/projectors/journal_entry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ defmodule JourniPlan.Itineraries.Projectors.JournalEntry do
alias JourniPlan.Itineraries.Projections.JournalEntry

project(%JournalEntryCreated{} = created, _, fn multi ->
{:ok, entry_date, _} = DateTime.from_iso8601(created.entry_date)
entry_date =
created.entry_date
|> Timex.parse!("%Y-%m-%dT%H:%M", :strftime)
|> Timex.to_datetime("Etc/UTC")
itinerary_uuid = cast_uuid!(created.itinerary_uuid)
activity_uuid = cast_uuid!(created.activity_uuid)

Expand Down
2 changes: 1 addition & 1 deletion lib/journi_plan_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule JourniPlanWeb.CoreComponents do
use Phoenix.Component

alias Phoenix.LiveView.JS
import JourniPlanWeb.Gettext
use Gettext, backend: JourniPlanWeb.Gettext

@doc """
Renders a modal.
Expand Down
12 changes: 12 additions & 0 deletions lib/journi_plan_web/controllers/page_html/home.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@
</ul>
</div>

<!-- Action Buttons -->
<div class="px-6 py-4 bg-gray-100 flex space-x-4">
<.link patch={~p"/itineraries/#{itinerary}/activities/new"}
class="inline-block px-4 py-2 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">
Add activity
</.link>
<.link patch={~p"/itineraries/#{itinerary}/journal_entries/new"}
class="inline-block px-4 py-2 bg-green-600 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-75">
Add journal entry
</.link>
</div>

<div class="px-6 py-4 bg-gray-100">
<a href={~p"/itineraries/#{itinerary.uuid}"} class="text-indigo-600 hover:text-indigo-900 font-medium">
View Details
Expand Down
2 changes: 1 addition & 1 deletion lib/journi_plan_web/gettext.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ defmodule JourniPlanWeb.Gettext do
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext, otp_app: :journi_plan
use Gettext.Backend, otp_app: :journi_plan
end
88 changes: 88 additions & 0 deletions lib/journi_plan_web/live/activity_live/form_component.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
defmodule JourniPlanWeb.ActivityLive.FormComponent do
use JourniPlanWeb, :live_component

alias JourniPlan.Itineraries

@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage activity records in your database.</:subtitle>
</.header>
<.simple_form
for={@form}
id="activity-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:name]} type="text" label="Name" />
<.input field={@form[:description]} type="text" label="Description" />
<.input field={@form[:start_time]} type="datetime-local" label="Start time" />
<.input field={@form[:end_time]} type="datetime-local" label="End time" />
<:actions>
<.button phx-disable-with="Saving...">Save Activity</.button>
</:actions>
</.simple_form>
</div>
"""
end

@impl true
def update(%{activity: activity, action: action} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign(:form, to_form(Itineraries.change_activity(activity, action), as: "activity"))}

end

@impl true
def handle_event("validate", %{"activity" => activity_params}, socket) do
changeset = Itineraries.change_activity(socket.assigns.activity, socket.assigns.action, activity_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate, as: "activity"))}
end

def handle_event("save", %{"activity" => activity_params}, socket) do
save_activity(socket, socket.assigns.action, activity_params)
end

defp save_activity(socket, :edit, activity_params) do
case Itineraries.update_activity(socket.assigns.activity, activity_params) do
{:ok, activity} ->
notify_parent({:saved, activity})

{:noreply,
socket
|> put_flash(:info, "Activity updated successfully")
|> push_patch(to: socket.assigns.patch)}

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end

defp save_activity(socket, :new, activity_params) do
user_id = socket.assigns.current_user.id
activity_params = Map.put(activity_params, "user_id", user_id)
activity_params = Map.put(activity_params, "itinerary_uuid", socket.assigns.itinerary_id)

case Itineraries.create_activity(activity_params) do
{:ok, activity} ->
notify_parent({:saved, activity})

{:noreply,
socket
|> put_flash(:info, "Activity created 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
56 changes: 56 additions & 0 deletions lib/journi_plan_web/live/activity_live/index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule JourniPlanWeb.ActivityLive.Index do
use JourniPlanWeb, :live_view

alias JourniPlan.Itineraries
alias JourniPlan.Itineraries.Projections.Activity

@impl true
def mount(params, _session, socket) do
current_user = socket.assigns.current_user

{
:ok,
socket
|> assign(:current_user, current_user)
|> assign(:itinerary_id, params["id"])
|> stream_configure(:activities, dom_id: & &1.uuid)
|> stream(:activities, Itineraries.list_user_activities(current_user.id))
}
end

@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end

defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Activity")
|> assign(:activity, Itineraries.get_activity!(id))
end

defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Activity")
|> assign(:activity, %Activity{})
end

defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Activities")
|> assign(:activity, nil)
end

@impl true
def handle_info({JourniPlanWeb.ActivityLive.FormComponent, {:saved, activity}}, socket) do
{:noreply, stream_insert(socket, :activities, activity)}
end

@impl true
def handle_event("delete", %{"id" => id}, socket) do
activity = Itineraries.get_activity!(id)
{:ok, _} = Itineraries.delete_activity(activity)

{:noreply, stream_delete(socket, :activities, activity)}
end
end
46 changes: 46 additions & 0 deletions lib/journi_plan_web/live/activity_live/index.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<.header>
Listing Activities
<:actions>
<.link patch={~p"/activities/new"}>
<.button>New Activity</.button>
</.link>
</:actions>
</.header>

<.table
id="activities"
rows={@streams.activities}
row_click={fn {_id, activity} -> JS.navigate(~p"/activities/#{activity}") end}
>
<:col :let={{_id, activity}} label="Name"><%= activity.name %></:col>
<:col :let={{_id, activity}} label="Description"><%= activity.description %></:col>
<:col :let={{_id, activity}} label="Start time"><%= activity.start_time %></:col>
<:col :let={{_id, activity}} label="End time"><%= activity.end_time %></:col>
<:action :let={{_id, activity}}>
<div class="sr-only">
<.link navigate={~p"/activities/#{activity}"}>Show</.link>
</div>
<.link patch={~p"/activities/#{activity}/edit"}>Edit</.link>
</:action>
<:action :let={{id, activity}}>
<.link
phx-click={JS.push("delete", value: %{id: activity.uuid}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>

<.modal :if={@live_action in [:new, :edit]} id="activity-modal" show on_cancel={JS.patch(~p"/activities")}>
<.live_component
module={JourniPlanWeb.ActivityLive.FormComponent}
id={@activity.uuid || :new}
title={@page_title}
action={@live_action}
activity={@activity}
current_user={@current_user}
itinerary_id={@itinerary_id}
patch={~p"/activities"}
/>
</.modal>
21 changes: 21 additions & 0 deletions lib/journi_plan_web/live/activity_live/show.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule JourniPlanWeb.ActivityLive.Show do
use JourniPlanWeb, :live_view

alias JourniPlan.Itineraries

@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end

@impl true
def handle_params(%{"id" => id}, _, socket) do
{:noreply,
socket
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:activity, Itineraries.get_activity!(id))}
end

defp page_title(:show), do: "Show Activity"
defp page_title(:edit), do: "Edit Activity"
end
29 changes: 29 additions & 0 deletions lib/journi_plan_web/live/activity_live/show.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<.header>
Activity <%= @activity.uuid %>
<:subtitle>This is a activity record from your database.</:subtitle>
<:actions>
<.link patch={~p"/activities/#{@activity}/show/edit"} phx-click={JS.push_focus()}>
<.button>Edit activity</.button>
</.link>
</:actions>
</.header>

<.list>
<:item title="Name"><%= @activity.name %></:item>
<:item title="Description"><%= @activity.description %></:item>
<:item title="Start time"><%= @activity.start_time %></:item>
<:item title="End time"><%= @activity.end_time %></:item>
</.list>

<.back navigate={~p"/activities"}>Back to activities</.back>

<.modal :if={@live_action == :edit} id="activity-modal" show on_cancel={JS.patch(~p"/activities/#{@activity}")}>
<.live_component
module={JourniPlanWeb.ActivityLive.FormComponent}
id={@activity.uuid}
title={@page_title}
action={@live_action}
activity={@activity}
patch={~p"/activities/#{@activity}"}
/>
</.modal>
Loading

0 comments on commit 6d982e2

Please sign in to comment.