Skip to content

Commit

Permalink
Add skills
Browse files Browse the repository at this point in the history
  • Loading branch information
lurnid committed Nov 21, 2024
1 parent 4d62e51 commit 74463c7
Show file tree
Hide file tree
Showing 14 changed files with 735 additions and 104 deletions.
8 changes: 7 additions & 1 deletion assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import Uploaders from "./uploaders"
import {Combobox, ComboboxOption} from "./combobox"

let Hooks = {}
Hooks.Combobox = Combobox
Hooks.ComboboxOption = ComboboxOption

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken},
uploaders: Uploaders
uploaders: Uploaders,
hooks: Hooks
})

// Show progress bar on live navigation and form submits
Expand Down
56 changes: 56 additions & 0 deletions assets/js/combobox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export const Combobox = {
mounted() {
const wrapperEl = this.el.closest('div')
const toggleButton = wrapperEl.querySelector('button[phx-click="toggle-options"]')
const myself = this.el.getAttribute('phx-target')

this.el.addEventListener('keydown', event => {
if (['ArrowUp', 'ArrowDown'].includes(event.key)) {
// Prevent cursor-move from first to last character
event.preventDefault()
}

if (event.key === 'Enter' && document.activeElement === this.el) {
// Prevent Submit when combobox is focused
event.preventDefault()

const listEl = wrapperEl.querySelector('ul')
const value = listEl && listEl.dataset.focusedOption || null

// The focused value are stored in a data attribute.
// So, send the selected value to the component.
this.pushEventTo(myself, "select-option", {option: value})
}
})

this.el.addEventListener('keyup', event => {
if (['Enter', 'Tab', 'Escape', 'ArrowUp', 'ArrowDown'].includes(event.key)) return

// Push the search phrase to the LiveComponent
this.pushEventTo(myself, "filter-options", {search_phrase: this.el.value})
})

toggleButton && toggleButton.addEventListener('click', event => {
this.el.focus()
})

this.handleEvent("set-input-value", data => {
// This is sent to all instances of this hook so we need to compare id:s
if (data.id !== this.el.id) return
this.el.value = data.label
})
}
}

export const ComboboxOption = {
mounted() {
this.el.addEventListener('mouseover', event => {
const el = event.target
const value = el.getAttribute('phx-value-option')
const target = el.getAttribute('phx-target')

this.pushEventTo(target, "set-focus-to", {value: value})
})
}
}

1 change: 0 additions & 1 deletion lib/maker_passport/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule MakerPassport.Accounts do
"""

import Ecto.Query, warn: false
import Ecto.Changeset

alias MakerPassport.Repo
alias MakerPassport.Maker.Profile
Expand Down
168 changes: 166 additions & 2 deletions lib/maker_passport/maker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule MakerPassport.Maker do
alias MakerPassport.Accounts
alias MakerPassport.Repo

alias MakerPassport.Maker.Profile
alias MakerPassport.Maker.{Profile, Skill, ProfileSkill}

@doc """
Returns the list of profiles.
Expand Down Expand Up @@ -52,7 +52,7 @@ defmodule MakerPassport.Maker do
** (Ecto.NoResultsError)
"""
def get_profile!(id), do: Repo.get!(Profile, id) |> Repo.preload(:user)
def get_profile!(id), do: Repo.get!(Profile, id) |> Repo.preload(:user) |> Repo.preload([:skills])

def get_profile_by_user_id!(user_id) do
user =
Expand All @@ -61,6 +61,17 @@ defmodule MakerPassport.Maker do

Repo.get!(Profile, user.profile.id)
|> Repo.preload(:user)
|> Repo.preload([:skills])
end

def get_profile_with_skills_by_user_id!(user_id) do
user =
Accounts.get_user!(user_id)
|> Repo.preload(:profile)

Repo.get!(Profile, user.profile.id)
|> Repo.preload(:user)
|> Repo.preload([:skills])
end


Expand Down Expand Up @@ -129,4 +140,157 @@ defmodule MakerPassport.Maker do
def change_profile(%Profile{} = profile, attrs \\ %{}) do
Profile.changeset(profile, attrs)
end

@doc """
Returns the list of skills.
## Examples
iex> list_skills()
[%Skill{}, ...]
"""
def list_skills do
Repo.all(Skill)
end

@doc """
Gets a single skill.
Raises `Ecto.NoResultsError` if the Skill does not exist.
## Examples
iex> get_skill!(123)
%Skill{}
iex> get_skill!(456)
** (Ecto.NoResultsError)
"""
def get_skill!(id), do: Repo.get!(Skill, id)

@doc """
Creates a skill.
## Examples
iex> create_skill(%{field: value})
{:ok, %Skill{}}
iex> create_skill(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_skill(attrs \\ %{}) do
%Skill{}
|> Skill.changeset(attrs)
|> Repo.insert()
end

@doc """
Updates a skill.
## Examples
iex> update_skill(skill, %{field: new_value})
{:ok, %Skill{}}
iex> update_skill(skill, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_skill(%Skill{} = skill, attrs) do
skill
|> Skill.changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a skill.
## Examples
iex> delete_skill(skill)
{:ok, %Skill{}}
iex> delete_skill(skill)
{:error, %Ecto.Changeset{}}
"""
def delete_skill(%Skill{} = skill) do
Repo.delete(skill)
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking skill changes.
## Examples
iex> change_skill(skill)
%Ecto.Changeset{data: %Skill{}}
"""
def change_skill(%Skill{} = skill, attrs \\ %{}) do
Skill.changeset(skill, attrs)
end

def add_skill(profile, skill_text) when is_binary(skill_text) do
skill =
case Repo.get_by(Skill, %{name: skill_text}) do
nil ->
%Skill{} |> Skill.changeset(%{name: skill_text}) |> Repo.insert!()

skill ->
skill
end

add_skill(profile, skill.id)
end

def add_skill(%Profile{} = profile, skill_id) do
add_skill(profile.id, skill_id)
end

def add_skill(profile_id, skill_id) do
ProfileSkill.changeset(%ProfileSkill{}, %{profile_id: profile_id, skill_id: skill_id})
|> Repo.insert()
end

def remove_skill(%Profile{} = profile, skill_id) do
profile_skill = Repo.get_by(ProfileSkill, %{profile_id: profile.id, skill_id: skill_id})
Repo.delete(profile_skill)
end

def search_skills(""), do: []

def search_skills(search_text) do
Repo.all(from s in Skill, where: ilike(s.name, ^"%#{search_text}%"))
end

def to_skill_list(skills) do
skills
|> Enum.map(&to_skill_tuple/1)
|> Enum.sort_by(&elem(&1, 0))
end

def to_skill_list(skills, profile_id) do
profile_skills = Repo.all(from ps in ProfileSkill, where: ps.profile_id == ^profile_id)
skill_ids_to_exclude = Enum.map(profile_skills, & &1.skill_id)
skills = Enum.reject(skills, &(&1.id in skill_ids_to_exclude))
skills
|> Enum.map(&to_skill_tuple/1)
|> Enum.sort_by(&elem(&1, 0))
end

defp to_skill_tuple(skill) do
{skill.name, skill.id}
end

def has_skill?(profile, skill_id) do
# Check if the profile has the skill by querying the database
Repo.exists?(from s in ProfileSkill,
where: s.profile_id == ^profile.id and s.skill_id == ^skill_id
)
end
end
6 changes: 6 additions & 0 deletions lib/maker_passport/maker/profile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule MakerPassport.Maker.Profile do
import Ecto.Changeset

alias MakerPassport.Accounts.User
alias MakerPassport.Maker.Skill

schema "profiles" do
field :bio, :string
Expand All @@ -15,6 +16,10 @@ defmodule MakerPassport.Maker.Profile do

belongs_to :user, User

many_to_many :skills, Skill,
join_through: "profile_skills",
on_replace: :delete

timestamps(type: :utc_datetime)
end

Expand All @@ -35,4 +40,5 @@ defmodule MakerPassport.Maker.Profile do
# Add other required fields
end
def profile_complete?(_user), do: false

end
22 changes: 22 additions & 0 deletions lib/maker_passport/maker/profile_skill.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule MakerPassport.Maker.ProfileSkill do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset

alias MakerPassport.Maker.{Profile, Skill}

schema "profile_skills" do

belongs_to :profile, Profile
belongs_to :skill, Skill

timestamps(type: :utc_datetime)
end

@doc false
def changeset(profile_skill, attrs) do
profile_skill
|> cast(attrs, [:profile_id, :skill_id])
|> validate_required([:profile_id, :skill_id])
end
end
22 changes: 22 additions & 0 deletions lib/maker_passport/maker/skill.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule MakerPassport.Maker.Skill do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset

alias MakerPassport.Maker.Profile

schema "skills" do
field :name, :string

many_to_many :profiles, Profile, join_through: "profile_skills"

timestamps(type: :utc_datetime)
end

@doc false
def changeset(skill, attrs \\ %{}) do
skill
|> cast(attrs, [:name])
|> validate_required([:name])
end
end
Loading

0 comments on commit 74463c7

Please sign in to comment.