Skip to content

Commit

Permalink
feat: add knockout stage bracket (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
feliciofilipe committed Aug 22, 2023
1 parent c1cbc56 commit 055c559
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 42 deletions.
3 changes: 2 additions & 1 deletion lib/cesium_cup/tournament/match.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule CesiumCup.Tournament.Match do

@required_fields ~w(date)a

@optional_fields ~w(home_team_id away_team_id group_id elimination_round_id state group_round)a
@optional_fields ~w(home_team_id away_team_id group_id elimination_round_index elimination_round_id state group_round)a

@states ~w(upcoming first_half halftime second_half finished)a

Expand All @@ -27,6 +27,7 @@ defmodule CesiumCup.Tournament.Match do

field :group_round, :integer

field :elimination_round_index, :integer
belongs_to :elimination_round, EliminationRound

has_many :events, Event
Expand Down
69 changes: 69 additions & 0 deletions lib/cesium_cup_web/components/bracket.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
defmodule CesiumCupWeb.Components.Bracket do
@moduledoc """
The knockout stage bracket component
"""
use Phoenix.LiveComponent

alias CesiumCup.Tournament
alias CesiumCupWeb.Router.Helpers, as: Routes

def render(assigns) do
~H"""
<div id={"bracket-#{@index}"}>
<%= if @current_round > 0 do %>
<div class="flex mr-20 items-center">
<div class="flex-col mb-4">
<.live_component module={CesiumCupWeb.Components.Bracket} id={"bracket-#{2 * @index + 1}"} index={2 * @index + 1} current_round={@current_round - 1} number_rounds={@number_rounds} matches={@matches} />
<.live_component module={CesiumCupWeb.Components.Bracket} id={"bracket-#{2 * @index + 2}"} index={2 * @index + 2} current_round={@current_round - 1} number_rounds={@number_rounds} matches={@matches} />
</div>
<% match = Enum.find(@matches, &(&1.elimination_round_index == @index)) %>
<%= live_redirect to: Routes.match_show_path(@socket, :show, match) do %>
<div class="cursor-pointer bg-white border border-zinc-200 rounded-md shadow-sm hover:bg-zinc-50 my-1 relative w-40 h-28">
<div class="h-full w-full flex-col divide-y divide-gray-200">
<div class="w-full flex justify-between items-center h-full h-1/2 text-gray-900 px-4 py-2">
<p>
<%= if match.home_team do %>
<%= match.home_team.name %>
<% else %>
TBD
<% end %>
</p>
<p>
<%= if not is_nil(match.home_team_id) and match.state != :upcoming do %>
<%= get_home_team_score(match.id) %>
<% end %>
</p>
</div>
<div class="w-full flex justify-between items-center h-full h-1/2 text-gray-900 px-4 py-2">
<p>
<%= if match.away_team do %>
<%= match.away_team.name %>
<% else %>
TBD
<% end %>
</p>
<p>
<%= if not is_nil(match.home_team_id) and match.state != :upcoming do %>
<%= get_away_team_score(match.id) %>
<% end %>
</p>
</div>
</div>
</div>
<% end %>
</div>
<% end %>
</div>
"""
end

defp get_home_team_score(match_id) do
Tournament.get_home_team_score(match_id)
end

defp get_away_team_score(match_id) do
Tournament.get_away_team_score(match_id)
end
end
5 changes: 5 additions & 0 deletions lib/cesium_cup_web/live/home_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ defmodule CesiumCupWeb.HomeLive.Index do
|> Enum.sort(&(Date.compare(&1.date, &2.date) in [:lt, :eq]))
end

defp list_knockout_stage_matches do
Tournament.list_matches(preloads: [:home_team, :away_team])
|> Enum.filter(&is_nil(&1.group_round))
end

defp list_groups do
Tournament.list_groups(preloads: [:teams])
end
Expand Down
93 changes: 52 additions & 41 deletions lib/cesium_cup_web/live/home_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,17 @@
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
end} whitespace-nowrap py-4 px-1 border-b-4 font-medium text-md") %>
<%= live_patch("Knockout Stage", to: "?tab=knockout_stage", class: "#{if @tab == "knockout_stage" do
"border-quinary text-gray-900"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
end} whitespace-nowrap py-4 px-1 border-b-4 font-medium text-md") %>
<%= for round <- @elimination_rounds do %>
<%= live_patch(round.name, to: "?tab=#{round.name}", class: "#{if @tab == round.name do
"border-quinary text-gray-900"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
end} whitespace-nowrap py-4 px-1 border-b-4 font-medium text-md") %>
end} show lg:hidden whitespace-nowrap py-4 px-1 border-b-4 font-medium text-md") %>
<% end %>
</nav>
</div>
Expand Down Expand Up @@ -197,52 +202,58 @@
</div>
</div>
<% else %>
<div class="flex flex-col my-10">
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table class="min-w-full divide-y divide-gray-300">
<tbody class="divide-y divide-gray-200 bg-white">
<%= for match <- Enum.sort(@selected_elimination_round.matches, &(Date.compare(&1.date, &2.date) in [:lt, :eq])) do %>
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm text-gray-500 sm:pl-6">
<%= display_date(match.date) %>
</td>
<td class="whitespace-nowrap py-4 pl-20 text-sm font-medium text-gray-900 text-left">
<%= if match.home_team_id do %>
<%= live_redirect to: Routes.team_show_path(@socket, :show, get_team(match.home_team_id)) do %>
<%= get_team(match.home_team_id).name %>
<% end %>
<% else %>
TBD
<% end %>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<%= live_redirect to: Routes.match_show_path(@socket, :show, match) do %>
<%= if match.state != :upcoming do %>
<%= get_home_team_score(match.id) %> - <%= get_away_team_score(match.id) %>
<%= if @tab != "knockout_stage" do %>
<div class="show lg:hidden flex flex-col my-10">
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table class="min-w-full divide-y divide-gray-300">
<tbody class="divide-y divide-gray-200 bg-white">
<%= for match <- Enum.sort(@selected_elimination_round.matches, &(Date.compare(&1.date, &2.date) in [:lt, :eq])) do %>
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm text-gray-500 sm:pl-6">
<%= display_date(match.date) %>
</td>
<td class="whitespace-nowrap py-4 pl-20 text-sm font-medium text-gray-900 text-left">
<%= if match.home_team_id do %>
<%= live_redirect to: Routes.team_show_path(@socket, :show, get_team(match.home_team_id)) do %>
<%= get_team(match.home_team_id).name %>
<% end %>
<% else %>
<%= display_time(match.date) %>
TBD
<% end %>
<% end %>
</td>
<td class="whitespace-nowrap py-4 pr-20 text-sm font-medium text-gray-900 text-right">
<%= if match.away_team_id do %>
<%= live_redirect to: Routes.team_show_path(@socket, :show, get_team(match.away_team_id)) do %>
<%= get_team(match.away_team_id).name %>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-center">
<%= live_redirect to: Routes.match_show_path(@socket, :show, match) do %>
<%= if match.state != :upcoming do %>
<%= get_home_team_score(match.id) %> - <%= get_away_team_score(match.id) %>
<% else %>
<%= display_time(match.date) %>
<% end %>
<% end %>
<% else %>
TBD
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
</td>
<td class="whitespace-nowrap py-4 pr-20 text-sm font-medium text-gray-900 text-right">
<%= if match.away_team_id do %>
<%= live_redirect to: Routes.team_show_path(@socket, :show, get_team(match.away_team_id)) do %>
<%= get_team(match.away_team_id).name %>
<% end %>
<% else %>
TBD
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<% else %>
<div class="my-4">
<.live_component module={CesiumCupWeb.Components.Bracket} id="bracket" index={0} current_round={length(@elimination_rounds)} number_rounds={length(@elimination_rounds)} matches={list_knockout_stage_matches()} />
</div>
<% end %>
<% end %>

<div>
Expand Down
6 changes: 6 additions & 0 deletions lib/cesium_cup_web/live/match_live/form_component.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
<%= error_tag(f, :group_id) %>
</li>

<li>
<%= label(f, :elimination_round_index) %>
<%= number_input(f, :elimination_round_index) %>
<%= error_tag(f, :elimination_round_index) %>
</li>

<li>
<%= label(f, :elimination_round_id) %>
<%= select(f, :elimination_round_id, Enum.map(@elimination_rounds, &{&1.name, &1.id}), prompt: "Choose a Elimination Round", selected: f.data.group_id) %>
Expand Down
2 changes: 2 additions & 0 deletions priv/repo/migrations/20221210233254_create_matches.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ defmodule CesiumCup.Repo.Migrations.CreateMatches do
add :home_team_id, references(:teams, on_delete: :nothing, type: :binary_id)
add :away_team_id, references(:teams, on_delete: :nothing, type: :binary_id)

add :elimination_round_index, :integer

add :elimination_round_id,
references(:elimination_rounds, on_delete: :nothing, type: :binary_id)

Expand Down
7 changes: 7 additions & 0 deletions priv/repo/seeds/schedule.exs
Original file line number Diff line number Diff line change
Expand Up @@ -221,36 +221,43 @@ defmodule CesiumCup.Repo.Seeds.Schedule do
%{
date: ~N[2023-12-02 15:00:00],
state: :upcoming,
elimination_round_index: 3,
elimination_round_id: get_elimination_round_id("Quarter-Finals")
},
%{
date: ~N[2023-12-03 15:00:00],
state: :upcoming,
elimination_round_index: 4,
elimination_round_id: get_elimination_round_id("Quarter-Finals")
},
%{
date: ~N[2023-12-04 15:00:00],
state: :upcoming,
elimination_round_index: 5,
elimination_round_id: get_elimination_round_id("Quarter-Finals")
},
%{
date: ~N[2023-12-05 15:00:00],
state: :upcoming,
elimination_round_index: 6,
elimination_round_id: get_elimination_round_id("Quarter-Finals")
},
%{
date: ~N[2023-12-06 16:00:00],
state: :upcoming,
elimination_round_index: 1,
elimination_round_id: get_elimination_round_id("Semi-Finals")
},
%{
date: ~N[2023-12-07 16:00:00],
state: :upcoming,
elimination_round_index: 2,
elimination_round_id: get_elimination_round_id("Semi-Finals")
},
%{
date: ~N[2023-12-10 20:00:00],
state: :upcoming,
elimination_round_index: 0,
elimination_round_id: get_elimination_round_id("Final")
}
]
Expand Down

0 comments on commit 055c559

Please sign in to comment.