Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add live views for activity and journal entry #23

Merged
merged 10 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading