Skip to content

Commit 27b448b

Browse files
committed
add activities dropdown in main nav and on company admin page
1 parent a40c3a0 commit 27b448b

File tree

5 files changed

+182
-17
lines changed

5 files changed

+182
-17
lines changed

lib/algora/activities/activities.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ defmodule Algora.Activities do
3434
}
3535

3636
@table_from_user_relation %{
37-
# attempts: "attempt_activities",
37+
# attempts: "attempt_activities",
3838
claims: "claim_activities",
3939
client_contracts: "contract_activities",
4040
connected_installations: "installation_activities",
@@ -45,7 +45,7 @@ defmodule Algora.Activities do
4545
owned_bounties: "bounty_activities",
4646
identities: "identity_activities",
4747
owned_installations: "installation_activities",
48-
# projects: "project_activities",
48+
# projects: "project_activities",
4949
repositories: "repository_activities",
5050
transactions: "transaction_activities"
5151
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
defmodule AlgoraWeb.Components.Activity do
2+
@moduledoc false
3+
use AlgoraWeb.Component
4+
5+
import AlgoraWeb.CoreComponents
6+
7+
attr :activities, :list, required: true
8+
9+
def activities_timeline(assigns) do
10+
~H"""
11+
<div class="space-y-2 h-[400px] overflow-y-auto">
12+
<%= for {_id, activity} <- assigns[:activities] do %>
13+
<div class="flex-grow hover:bg-accent">
14+
<.activity_card activity={activity} />
15+
</div>
16+
<% end %>
17+
</div>
18+
"""
19+
end
20+
21+
attr :activity, :map, required: true
22+
23+
def activity_card(assigns) do
24+
~H"""
25+
<.link
26+
href={url_for_activity(assigns[:activity])}
27+
class="flex flex-grow items-center gap-4 mt-4 mb-4 p-4 pb-4 border-b w-full last:border-none first:border-t first:mt-0 first:pt-4"
28+
tabindex="-1"
29+
>
30+
<div class={[
31+
"flex h-9 w-9 items-center justify-center rounded-full",
32+
activity_background_class(@activity.type)
33+
]}>
34+
<.icon name={activity_icon(to_string(@activity.type))} class="h-5 w-5" />
35+
</div>
36+
<div class="flex-1">
37+
<div class="font-medium">
38+
<.activity_name type={assigns.activity.type} />
39+
</div>
40+
<div class="text-sm text-muted-foreground">
41+
{Calendar.strftime(assigns.activity.inserted_at, "%b %d, %Y, %H:%M:%S")}
42+
</div>
43+
</div>
44+
</.link>
45+
"""
46+
end
47+
48+
attr :type, :atom, required: true
49+
50+
def activity_name(%{type: type} = assigns) do
51+
assigns = assign(assigns, :name, activity_type_to_name(type))
52+
53+
~H"""
54+
<div class="">{@name}</div>
55+
"""
56+
end
57+
58+
attr :id, :string, required: true
59+
attr :class, :string, default: nil
60+
attr :activities, :list, required: true
61+
62+
def dropdown_activities(assigns) do
63+
~H"""
64+
<div class={classes(["relative w-full text-left", @class])}>
65+
<div>
66+
<button
67+
id={@id}
68+
type="button"
69+
class="group w-full rounded-md px-3.5 py-2 text-left text-sm font-medium text-foreground hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring"
70+
phx-click={show_dropdown("##{@id}-dropdown")}
71+
phx-hook="Menu"
72+
data-active-class="bg-accent"
73+
aria-haspopup="true"
74+
>
75+
<span class="flex w-full items-center justify-between">
76+
<span class="flex min-w-0 items-center justify-between space-x-3">
77+
<.icon name="tabler-activity" class="h-15 w-15 shrink-0" />
78+
</span>
79+
<.icon
80+
name="tabler-selector"
81+
class="ml-2 h-5 w-5 flex-shrink-0 text-gray-500 group-hover:text-gray-400"
82+
/>
83+
</span>
84+
</button>
85+
</div>
86+
<div
87+
id={"#{@id}-dropdown"}
88+
phx-click-away={hide_dropdown("##{@id}-dropdown")}
89+
class="absolute mt-4 p-0 right-[200px] left-[-160px] z-10 mt-1 hidden origin-top w-[400px] divide-border rounded-md bg-popover shadow-lg ring-1 ring-border shadow-[0_0_1px_rgba(255,255,255,0.5)]"
90+
role="menu"
91+
aria-labelledby={@id}
92+
>
93+
<div class="py-1 w-full overflow-y-auto" role="none">
94+
<.activities_timeline activities={@activities} />
95+
</div>
96+
</div>
97+
</div>
98+
"""
99+
end
100+
101+
defp url_for_activity(%{type: :identity_created, assoc: assoc}), do: "/@/#{assoc.provider_login}"
102+
103+
defp url_for_activity(activity) do
104+
slug = activity.assoc_name |> to_string() |> String.replace("_activities", "")
105+
"/a/#{slug}/#{activity.id}"
106+
end
107+
108+
defp activity_type_to_name(type) do
109+
type
110+
|> to_string()
111+
|> String.split("_")
112+
|> Enum.map_join(" ", &String.capitalize(&1))
113+
end
114+
115+
defp activity_icon(type) do
116+
"tabler-file-check"
117+
end
118+
119+
defp activity_background_class(type) do
120+
"bg-primary/20"
121+
end
122+
end

lib/algora_web/components/layouts/user.html.heex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,12 @@
170170
<span class="relative text-sm font-semibold">Docs</span>
171171
</.link>
172172
<div>
173-
<%= live_render(@socket, AlgoraWeb.Activity.UserNavTimelineLive, id: "activity-timeline", session: %{}, sticky: true, assigns: %{current_user: @current_user}) %>
173+
{live_render(@socket, AlgoraWeb.Activity.UserNavTimelineLive,
174+
id: "activity-timeline",
175+
session: %{},
176+
sticky: true,
177+
assigns: %{current_user: @current_user}
178+
)}
174179
</div>
175180
<%= if @current_user do %>
176181
<div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule AlgoraWeb.Activity.UserNavTimelineLive do
2+
@moduledoc false
3+
use AlgoraWeb, :live_view
4+
5+
import AlgoraWeb.Components.Activity
6+
7+
def mount(_params, %{"user_id" => user_id} = session, socket) when is_binary(user_id) do
8+
{:ok,
9+
socket
10+
|> stream(:activities, [])
11+
|> start_async(:get_activities, fn -> Algora.Activities.all_for_user(user_id) end)}
12+
end
13+
14+
def handle_async(:get_activities, {:ok, fetched}, socket) do
15+
{:noreply, stream(socket, :activities, fetched)}
16+
end
17+
18+
def render(assigns) do
19+
~H"""
20+
<.dropdown_activities activities={@streams.activities} id="activities-dropdown" />
21+
"""
22+
end
23+
end

lib/algora_web/live/admin/company_analytics_live.ex

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ defmodule AlgoraWeb.Admin.CompanyAnalyticsLive do
22
@moduledoc false
33
use AlgoraWeb, :live_view
44

5+
import AlgoraWeb.Components.Activity
6+
57
alias Algora.Activities
68
alias Algora.Analytics
79

8-
import AlgoraWeb.Components.Activity
9-
1010
def mount(_params, _session, socket) do
1111
analytics = Analytics.get_company_analytics()
1212
funnel_data = Analytics.get_funnel_data()
@@ -18,7 +18,6 @@ defmodule AlgoraWeb.Admin.CompanyAnalyticsLive do
1818
|> assign(:selected_period, "30d")
1919
|> stream(:activities, [])
2020
|> start_async(:get_activities, fn -> Activities.all() end)}
21-
2221
end
2322

2423
def render(assigns) do
@@ -37,17 +36,29 @@ defmodule AlgoraWeb.Admin.CompanyAnalyticsLive do
3736
</.button>
3837
</div>
3938
</div>
40-
<!-- Funnel Chart -->
41-
<.card>
42-
<.card_header>
43-
<.card_title>Company Funnel</.card_title>
44-
</.card_header>
45-
<.card_content>
46-
<div class="h-[400px]" phx-update="ignore" id="funnel-chart">
47-
<canvas id="funnelChart"></canvas>
48-
</div>
49-
</.card_content>
50-
</.card>
39+
40+
<div class="mx-auto h-500 flex">
41+
<div class="w-3/4 p-0">
42+
<.card>
43+
<.card_header>
44+
<.card_title>Company Funnel</.card_title>
45+
</.card_header>
46+
<.card_content>
47+
<div class="h-[400px]" phx-update="ignore" id="funnel-chart">
48+
<canvas id="funnelChart"></canvas>
49+
</div>
50+
</.card_content>
51+
</.card>
52+
</div>
53+
<.scroll_area class="w-1/4 ml-4 pr-4">
54+
<.card class="h-[500px]">
55+
<.card_header>
56+
<.card_title>Recent Activities</.card_title>
57+
</.card_header>
58+
<.activities_timeline activities={@streams.activities} />
59+
</.card>
60+
</.scroll_area>
61+
</div>
5162
<!-- Key Metrics -->
5263
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
5364
<.stat_card
@@ -149,4 +160,8 @@ defmodule AlgoraWeb.Admin.CompanyAnalyticsLive do
149160
|> assign(:funnel_data, funnel_data)
150161
|> assign(:selected_period, period)}
151162
end
163+
164+
def handle_async(:get_activities, {:ok, fetched}, socket) do
165+
{:noreply, stream(socket, :activities, fetched)}
166+
end
152167
end

0 commit comments

Comments
 (0)