Repository navigation
Add dispatcher Community Fridges management with list/map views, location, and photo upload #540
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
Open
neal-bpm
wants to merge
53
commits into
main
Choose a base branch
from
vn/community-fridge-feature
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
68b5e83
Add CommunityFridge data model
neal-bpm be328c4
Fix parameter name in changeset function for CommunityFridge module
neal-bpm e841246
Community Fridge CRUD operation added
neal-bpm 32365d6
Addressed the review comments
neal-bpm f38a1bc
Refactor CommunityFridgeTest to use alias for Location and update tes…
neal-bpm d7c2e06
Merge branch 'main' into vn/community-fridges-data-model-v1
neal-bpm 3c70e80
Defer community fridge location fixture improvement to separate PR
neal-bpm 6226e42
Enforce one community fridge per location via unique_constraint
neal-bpm 702a77b
Merge branch 'vn/community-fridges-data-model-v1' into vn/community-f…
neal-bpm be26bd2
Add community fridge fixtures and comprehensive test coverage
neal-bpm 0a046ab
Merge branch 'main' into vn/community-fridge-crud-v1
neal-bpm 55476d1
Merge branch 'main' into vn/community-fridge-ui-v1
neal-bpm a4f815f
Add Community Fridge dispatcher UI to dispatchers only.
neal-bpm c3c5d45
Merge branch 'main' into vn/community-fridges-data-model-v1
neal-bpm 5e0859f
Add photo upload UI to community fridge form
neal-bpm ea5e3ce
Add Cancel button to community fridge form actions
neal-bpm 14d55d9
Add cancel button to community fridge form
neal-bpm d3a2509
Add CommunityFridge data model
neal-bpm 25b84a3
Fix parameter name in changeset function for CommunityFridge module
neal-bpm 727cb30
Community Fridge CRUD operation added
neal-bpm e3f3813
Addressed the review comments
neal-bpm 58ee025
Refactor CommunityFridgeTest to use alias for Location and update tes…
neal-bpm 36eeb07
Defer community fridge location fixture improvement to separate PR
neal-bpm 34cd1c7
Enforce one community fridge per location via unique_constraint
neal-bpm 8849b2b
Add community fridge fixtures and comprehensive test coverage
neal-bpm dd8e389
Merge branch 'vn/community-fridges-data-model-v1' into vn/community-f…
mveytsman 281eb5e
Merge branch 'main' into vn/community-fridge-crud-v1
neal-bpm b9eadc7
Merge remote-tracking branch 'origin/vn/community-fridge-crud-v1' int…
neal-bpm 3fc3a85
Enhance community fridge functionality with location support and map …
neal-bpm 5bb96b4
Implement mode switching between list and map views in community frid…
neal-bpm b77e307
Fix redundant DB query and register :community_fridges page in layout…
neal-bpm fd8674a
Merge branch 'vn/community-fridge-ui-v1' into vn/community-fridge-pho…
neal-bpm fc379dc
Enhance photo upload functionality: set max file size and add delete …
neal-bpm 05b4646
Refactor photo input field for improved readability
neal-bpm aab451b
Remove unnecessary blank line in Community Fridge photo upload tests
neal-bpm 821fe3f
Merge branch 'vn/community-fridge-photo-upload-v1' into vn/community-…
neal-bpm 3a219c0
addressed merge conflicts
neal-bpm e31545b
Refactor CommunityFridgeLive components: remove unused Repo alias and…
neal-bpm 6905d40
Merge branch 'vn/community-fridge-location-mapping-v1' into vn/commun…
neal-bpm 277cc3d
Update CommunityFridgeLive: set default location for new fridge and r…
neal-bpm 7763175
Refactor apply_action in CommunityFridgeLive to directly list communi…
neal-bpm 0c8390d
Merge branch 'main' into vn/community-fridge-ui-v1
mveytsman c4e8ee5
Remove unused alias for Locations in CommunityFridgeLiveTest
neal-bpm 3aa6f8c
Merge branch 'vn/community-fridge-ui-v1' into vn/community-fridge-fea…
neal-bpm caf254e
Merge branch 'vn/community-fridge-photo-upload-v1' into vn/community-…
neal-bpm cc754fb
Merge branch 'vn/community-fridge-location-mapping-v1' into vn/commun…
neal-bpm 52f8021
Merge branch 'vn/community-fridge-ui-fixes' into vn/community-fridge-…
neal-bpm 8835201
Update community fridge tests and seed data with location coordinates
neal-bpm 300eca3
Simplify CommunityFridge location handling to match project conventions
neal-bpm b9dc11c
Add location handling to community fridge creation test
neal-bpm 7d151d1
Remove unique constraint on location_id in community fridge schema an…
neal-bpm 0e96e2a
remove unique_index from location_id in CommunityFridges table
neal-bpm c5e7b73
Refactor delete_photo event to streamline changeset update for photo …
neal-bpm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
lib/bike_brigade_web/live/community_fridge_live/form_component.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| defmodule BikeBrigadeWeb.CommunityFridgeLive.FormComponent do | ||
| use BikeBrigadeWeb, :live_component | ||
|
|
||
| alias BikeBrigade.{Locations, MediaStorage} | ||
| alias BikeBrigadeWeb.Components.LiveLocation | ||
|
|
||
| @impl true | ||
| def mount(socket) do | ||
| {:ok, | ||
| allow_upload(socket, :photo, | ||
| accept: ~w(.gif .png .jpg .jpeg), | ||
| max_entries: 1, | ||
| max_file_size: 10_000_000 | ||
| )} | ||
| end | ||
|
|
||
| @impl true | ||
| def update(%{community_fridge: community_fridge} = assigns, socket) do | ||
| changeset = Locations.change_community_fridge(community_fridge) | ||
|
|
||
| {:ok, | ||
| socket | ||
| |> assign(assigns) | ||
| |> assign(:changeset, changeset)} | ||
| end | ||
|
|
||
| @impl true | ||
| def handle_event("validate", %{"community_fridge" => params}, socket) do | ||
| changeset = | ||
| socket.assigns.community_fridge | ||
| |> Locations.change_community_fridge(params) | ||
| |> Map.put(:action, :validate) | ||
|
|
||
| {:noreply, assign(socket, :changeset, changeset)} | ||
| end | ||
|
|
||
| def handle_event("save", %{"community_fridge" => params}, socket) do | ||
| params = | ||
| case consume_uploaded_entries(socket, :photo, fn %{path: path}, | ||
| %{client_type: content_type} -> | ||
| {:ok, MediaStorage.upload_file!(path, content_type)} | ||
| end) do | ||
| [%{url: url}] -> Map.put(params, "photo", url) | ||
| [] -> Map.update(params, "photo", nil, &if(&1 == "", do: nil, else: &1)) | ||
| end | ||
|
|
||
| save_community_fridge(socket, socket.assigns.action, params) | ||
| end | ||
|
|
||
| def handle_event("delete_photo", _params, socket) do | ||
| params = socket.assigns.changeset.params || %{} | ||
|
|
||
| {:noreply, | ||
| socket | ||
| |> assign( | ||
| :changeset, | ||
| socket.assigns.community_fridge | ||
| |> Locations.change_community_fridge(Map.put(params, "photo", nil)) | ||
| |> Map.put(:action, :validate) | ||
| )} | ||
| end | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| def handle_event("cancel_upload", %{"ref" => ref}, socket) do | ||
| {:noreply, cancel_upload(socket, :photo, ref)} | ||
| end | ||
|
|
||
| defp error_to_string(:too_large), do: "Too large" | ||
| defp error_to_string(:too_many_files), do: "You have selected too many files" | ||
| defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type" | ||
|
|
||
| defp save_community_fridge(socket, :edit, params) do | ||
| case Locations.update_community_fridge(socket.assigns.community_fridge, params) do | ||
| {:ok, _community_fridge} -> | ||
| {:noreply, | ||
| socket | ||
| |> put_flash(:info, "Community fridge updated successfully") | ||
| |> push_patch(to: socket.assigns.navigate)} | ||
|
|
||
| {:error, %Ecto.Changeset{} = changeset} -> | ||
| {:noreply, assign(socket, :changeset, changeset)} | ||
| end | ||
| end | ||
|
|
||
| defp save_community_fridge(socket, :new, params) do | ||
| case Locations.create_community_fridge(params) do | ||
| {:ok, _community_fridge} -> | ||
| {:noreply, | ||
| socket | ||
| |> put_flash(:info, "Community fridge created successfully") | ||
| |> push_patch(to: socket.assigns.navigate)} | ||
|
|
||
| {:error, %Ecto.Changeset{} = changeset} -> | ||
| {:noreply, assign(socket, changeset: changeset)} | ||
| end | ||
| end | ||
| end | ||
100 changes: 100 additions & 0 deletions
100
lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| <div> | ||
| <.header>{@title}</.header> | ||
| <.simple_form | ||
| :let={f} | ||
| for={@changeset} | ||
| id="community-fridge-form" | ||
| phx-target={@myself} | ||
| phx-change="validate" | ||
| phx-submit="save" | ||
| > | ||
| <.input type="text" field={f[:name]} label="Name" /> | ||
|
|
||
| <.input type="textarea" field={f[:description]} label="Description" /> | ||
|
|
||
| <.live_component | ||
| module={LiveLocation} | ||
| id="location-form" | ||
| field={f[:location]} | ||
| label="Location" | ||
| /> | ||
|
|
||
| <div> | ||
| <label class="block text-sm font-semibold leading-6 text-zinc-800">Photo</label> | ||
| <input | ||
| type="hidden" | ||
| name={Phoenix.HTML.Form.input_name(f, :photo)} | ||
| value={Phoenix.HTML.Form.input_value(f, :photo) || ""} | ||
| /> | ||
| <figure :if={Phoenix.HTML.Form.input_value(f, :photo) not in [nil, ""]} class="mt-2"> | ||
| <img class="w-32 rounded" src={Phoenix.HTML.Form.input_value(f, :photo)} /> | ||
| <figcaption class="flex mt-1"> | ||
| <.button | ||
| color={:lightred} | ||
| size={:xsmall} | ||
| phx-click={JS.push("delete_photo", target: @myself)} | ||
| type="button" | ||
| > | ||
| Delete | ||
| </.button> | ||
| </figcaption> | ||
| </figure> | ||
| <div | ||
| class="flex justify-center px-6 py-10 mt-2 border border-dashed rounded-lg border-gray-900/25" | ||
| phx-drop-target={@uploads.photo.ref} | ||
| > | ||
| <div class="text-center"> | ||
| <Heroicons.photo solid class="w-12 h-12 mx-auto text-gray-300" /> | ||
| <div class="flex mt-4 text-sm leading-6 text-gray-600"> | ||
| <label | ||
| for={@uploads.photo.ref} | ||
| class="relative font-semibold text-indigo-600 bg-white rounded-md cursor-pointer focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500" | ||
| > | ||
| <span>Upload a file</span> | ||
| <.live_file_input upload={@uploads.photo} class="sr-only" /> | ||
| </label> | ||
| <p class="pl-1">or drag and drop</p> | ||
| </div> | ||
| <p class="text-xs leading-5 text-gray-600">PNG, JPG, GIF up to 10MB</p> | ||
| </div> | ||
| </div> | ||
| <div class="mt-2"> | ||
| <figure :for={entry <- @uploads.photo.entries} class="flex items-center gap-3 mt-2"> | ||
| <.live_img_preview entry={entry} class="w-16 rounded" /> | ||
| <div class="text-sm text-gray-600">{entry.client_name}</div> | ||
| <button | ||
| type="button" | ||
| phx-click={JS.push("cancel_upload", value: %{ref: entry.ref}, target: @myself)} | ||
| aria-label="cancel" | ||
| class="text-gray-400 hover:text-gray-600" | ||
| > | ||
| × | ||
| </button> | ||
| <p :for={err <- upload_errors(@uploads.photo, entry)} class="text-sm text-red-600"> | ||
| {error_to_string(err)} | ||
| </p> | ||
| </figure> | ||
| </div> | ||
| </div> | ||
|
|
||
| <.input | ||
| type="checkbox" | ||
| field={f[:active]} | ||
| label="Active" | ||
| /> | ||
|
|
||
| <.input | ||
| type="checkbox" | ||
| field={f[:pair_preferred]} | ||
| label="Pair Preferred" | ||
| help_text="Deliveries to this fridge prefer a rider pair" | ||
| /> | ||
|
|
||
| <:actions> | ||
| <.button navigate={~p"/community_fridges"} color={:white}> | ||
| Cancel | ||
| </.button> | ||
| <.button type="submit" phx-disable-with="Saving...">Save</.button> | ||
| </:actions> | ||
| </.simple_form> | ||
| </div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| defmodule BikeBrigadeWeb.CommunityFridgeLive.Index do | ||
| use BikeBrigadeWeb, :live_view | ||
|
|
||
| alias BikeBrigade.Locations | ||
| alias BikeBrigade.Locations.CommunityFridge | ||
|
|
||
| # Toronto city hall as fallback centre | ||
| @default_coords %Geo.Point{coordinates: {-79.3832, 43.6532}} | ||
|
|
||
| @impl Phoenix.LiveView | ||
| def mount(_params, _session, socket) do | ||
| {:ok, | ||
| socket | ||
| |> assign(:page, :community_fridges) | ||
| |> assign(:page_title, "Community Fridges") | ||
| |> assign(:community_fridge, nil) | ||
| |> assign(:community_fridges, Locations.list_community_fridges()) | ||
| |> assign(:map_layers, []) | ||
| |> assign(:map_coords, @default_coords) | ||
| |> assign(:mode, :list)} | ||
| end | ||
|
|
||
| @impl Phoenix.LiveView | ||
| def handle_params(params, _url, socket) do | ||
| {:noreply, apply_action(socket, socket.assigns.live_action, params)} | ||
| end | ||
|
|
||
| @impl Phoenix.LiveView | ||
| def handle_event("set_mode", %{"mode" => mode}, socket) do | ||
| socket = assign(socket, :mode, String.to_existing_atom(mode)) | ||
|
|
||
| socket = | ||
| if socket.assigns.mode == :map do | ||
| push_event(socket, "leaflet:redraw_map", %{recenter: false}) | ||
| else | ||
| socket | ||
| end | ||
|
|
||
| {:noreply, socket} | ||
| end | ||
|
|
||
| defp apply_action(socket, :index, _params) do | ||
| community_fridges = Locations.list_community_fridges() | ||
|
|
||
| socket | ||
| |> assign(:page_title, "Community Fridges") | ||
| |> assign(:community_fridge, nil) | ||
| |> assign(:community_fridges, community_fridges) | ||
| |> assign(:map_layers, fridge_markers(community_fridges)) | ||
| |> assign(:map_coords, map_center(community_fridges)) | ||
| end | ||
|
|
||
| defp apply_action(socket, :new, _params) do | ||
| socket | ||
| |> assign(:page_title, "New Fridge") | ||
| |> assign(:community_fridge, %CommunityFridge{location: nil}) | ||
| end | ||
|
|
||
| defp apply_action(socket, :edit, %{"id" => id}) do | ||
| socket | ||
| |> assign(:page_title, "Edit Fridge") | ||
| |> assign(:community_fridge, Locations.get_community_fridge!(id)) | ||
| end | ||
|
|
||
| defp fridge_markers(community_fridges) do | ||
| for %{id: id, name: name, location: location} <- community_fridges, | ||
| not is_nil(location), | ||
| not is_nil(location.coords) do | ||
| %{ | ||
| id: "fridge-#{id}", | ||
| type: :marker, | ||
| data: %{ | ||
| lat: lat(location), | ||
| lng: lng(location), | ||
| icon: "warehouse", | ||
| color: "#1c64f2", | ||
| tooltip: name | ||
| } | ||
| } | ||
| end | ||
| end | ||
|
|
||
| defp map_center(community_fridges) do | ||
| community_fridges | ||
| |> Enum.find_value(fn %{location: location} -> | ||
| location && location.coords | ||
| end) | ||
| |> case do | ||
| nil -> @default_coords | ||
| coords -> coords | ||
| end | ||
| end | ||
| end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.