refactor(elixir): remove unused is_active field

from storage location
This commit is contained in:
Schuwi
2025-09-17 23:13:45 +02:00
parent 6a1122c3be
commit 5a1775e836
4 changed files with 286 additions and 149 deletions

View File

@@ -97,6 +97,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
def handle_event("category_filter", %{"category_id" => category_id}, socket) do
category_id = String.to_integer(category_id)
{:noreply,
socket
|> assign(:selected_category, category_id)
@@ -119,20 +120,26 @@ defmodule ComponentsElixirWeb.ComponentsLive do
case Inventory.increment_component_count(component) do
{:ok, _updated_component} ->
# Only apply sort freeze for dynamic sorting criteria
should_freeze = socket.assigns.sort_criteria in ["count_asc", "count_desc", "updated_at_asc", "updated_at_desc"]
should_freeze =
socket.assigns.sort_criteria in [
"count_asc",
"count_desc",
"updated_at_asc",
"updated_at_desc"
]
if should_freeze do
# Cancel any existing timer
if socket.assigns.sort_freeze_timer do
Process.cancel_timer(socket.assigns.sort_freeze_timer)
end
# Set sort freeze for 3 seconds and mark component as interacting
freeze_until = DateTime.add(DateTime.utc_now(), 3, :second)
# Set new timer to clear interaction state
timer_ref = Process.send_after(self(), {:clear_interaction, id}, 3000)
{:noreply,
socket
|> put_flash(:info, "Count updated")
@@ -160,20 +167,26 @@ defmodule ComponentsElixirWeb.ComponentsLive do
case Inventory.decrement_component_count(component) do
{:ok, _updated_component} ->
# Only apply sort freeze for dynamic sorting criteria
should_freeze = socket.assigns.sort_criteria in ["count_asc", "count_desc", "updated_at_asc", "updated_at_desc"]
should_freeze =
socket.assigns.sort_criteria in [
"count_asc",
"count_desc",
"updated_at_asc",
"updated_at_desc"
]
if should_freeze do
# Cancel any existing timer
if socket.assigns.sort_freeze_timer do
Process.cancel_timer(socket.assigns.sort_freeze_timer)
end
# Set sort freeze for 3 seconds and mark component as interacting
freeze_until = DateTime.add(DateTime.utc_now(), 3, :second)
# Set new timer to clear interaction state
timer_ref = Process.send_after(self(), {:clear_interaction, id}, 3000)
{:noreply,
socket
|> put_flash(:info, "Count updated")
@@ -269,9 +282,11 @@ defmodule ComponentsElixirWeb.ComponentsLive do
new_focused_id =
if socket.assigns.focused_component_id == component_id do
nil # Unfocus if clicking on the same component
# Unfocus if clicking on the same component
nil
else
component_id # Focus on the new component
# Focus on the new component
component_id
end
{:noreply, assign(socket, :focused_component_id, new_focused_id)}
@@ -349,23 +364,26 @@ defmodule ComponentsElixirWeb.ComponentsLive do
# Check if sorting should be frozen
now = DateTime.utc_now()
should_reload = is_nil(socket.assigns.sort_freeze_until) ||
DateTime.compare(now, socket.assigns.sort_freeze_until) != :lt
should_reload =
is_nil(socket.assigns.sort_freeze_until) ||
DateTime.compare(now, socket.assigns.sort_freeze_until) != :lt
if should_reload do
# Normal loading - query database with current sort criteria
filters = [
search: socket.assigns.search,
sort_criteria: socket.assigns.sort_criteria,
category_id: socket.assigns.selected_category,
limit: @items_per_page,
offset: socket.assigns.offset
]
|> Enum.reject(fn
{_, v} when is_nil(v) -> true
{:search, v} when v == "" -> true
{_, _} -> false
end)
filters =
[
search: socket.assigns.search,
sort_criteria: socket.assigns.sort_criteria,
category_id: socket.assigns.selected_category,
limit: @items_per_page,
offset: socket.assigns.offset
]
|> Enum.reject(fn
{_, v} when is_nil(v) -> true
{:search, v} when v == "" -> true
{_, _} -> false
end)
%{components: new_components, has_more: has_more} =
Inventory.paginate_components(filters)
@@ -383,7 +401,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
else
# Frozen - just update the specific component in place without reordering
if socket.assigns.interacting_with do
updated_components =
updated_components =
Enum.map(socket.assigns.components, fn component ->
if to_string(component.id) == socket.assigns.interacting_with do
# Reload this specific component to get updated count
@@ -392,7 +410,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
component
end
end)
assign(socket, :components, updated_components)
else
socket
@@ -485,8 +503,8 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</div>
</div>
</div>
<!-- Filters -->
<!-- Filters -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex-1">
@@ -553,7 +571,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<%= if @sort_frozen do %>
<div class="absolute -bottom-5 left-0 text-xs text-yellow-600 flex items-center transition-opacity duration-200 pointer-events-none">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2L3 7v11h14V7l-7-5z"/>
<path d="M10 2L3 7v11h14V7l-7-5z" />
</svg>
Sort temporarily frozen
</div>
@@ -562,7 +580,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</div>
</div>
</div>
<!-- Add Component Modal -->
<%= if @show_add_form do %>
<div class="fixed inset-0 bg-base-content/50 overflow-y-auto h-full w-full z-50">
@@ -578,7 +596,13 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</button>
</div>
<.form for={@form} phx-submit="save_component" phx-change="validate" multipart={true} class="space-y-4">
<.form
for={@form}
phx-submit="save_component"
phx-change="validate"
multipart={true}
class="space-y-4"
>
<div>
<label class="block text-sm font-medium text-base-content">Name</label>
<.input field={@form[:name]} type="text" required />
@@ -605,7 +629,9 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<.input field={@form[:keywords]} type="text" />
</div>
<div>
<label class="block text-sm font-medium text-base-content">Storage Location</label>
<label class="block text-sm font-medium text-base-content">
Storage Location
</label>
<.input
field={@form[:storage_location_id]}
type="select"
@@ -628,14 +654,17 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<div>
<label class="block text-sm font-medium text-base-content">Component Image</label>
<div class="mt-1">
<.live_file_input upload={@uploads.image} class="block w-full text-sm text-base-content file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-primary/10 file:text-primary hover:file:bg-primary/20" />
<.live_file_input
upload={@uploads.image}
class="block w-full text-sm text-base-content file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-primary/10 file:text-primary hover:file:bg-primary/20"
/>
</div>
<p class="mt-1 text-xs text-base-content/60">
JPG, PNG, GIF up to 5MB
</p>
<%= for err <- upload_errors(@uploads.image) do %>
<p class="text-error text-sm mt-1"><%= Phoenix.Naming.humanize(err) %></p>
<p class="text-error text-sm mt-1">{Phoenix.Naming.humanize(err)}</p>
<% end %>
<%= for entry <- @uploads.image.entries do %>
@@ -645,17 +674,23 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<.live_img_preview entry={entry} class="h-10 w-10 rounded-lg object-cover" />
</div>
<div class="ml-3">
<p class="text-sm font-medium text-base-content"><%= entry.client_name %></p>
<p class="text-sm text-base-content/60"><%= entry.progress %>%</p>
<p class="text-sm font-medium text-base-content">{entry.client_name}</p>
<p class="text-sm text-base-content/60">{entry.progress}%</p>
</div>
</div>
<button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref} aria-label="cancel" class="text-error hover:text-error/80">
<button
type="button"
phx-click="cancel-upload"
phx-value-ref={entry.ref}
aria-label="cancel"
class="text-error hover:text-error/80"
>
<.icon name="hero-x-mark" class="w-5 h-5" />
</button>
</div>
<% end %>
<%= for err <- upload_errors(@uploads.image) do %>
<p class="mt-1 text-sm text-error"><%= upload_error_to_string(err) %></p>
<p class="mt-1 text-sm text-error">{upload_error_to_string(err)}</p>
<% end %>
</div>
@@ -679,7 +714,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</div>
</div>
<% end %>
<!-- Edit Component Modal -->
<%= if @show_edit_form do %>
<div class="fixed inset-0 bg-base-content/50 overflow-y-auto h-full w-full z-50">
@@ -695,7 +730,13 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</button>
</div>
<.form for={@form} phx-submit="save_edit" phx-change="validate" multipart={true} class="space-y-4">
<.form
for={@form}
phx-submit="save_edit"
phx-change="validate"
multipart={true}
class="space-y-4"
>
<div>
<label class="block text-sm font-medium text-base-content">Name</label>
<.input field={@form[:name]} type="text" required />
@@ -722,7 +763,9 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<.input field={@form[:keywords]} type="text" />
</div>
<div>
<label class="block text-sm font-medium text-base-content">Storage Location</label>
<label class="block text-sm font-medium text-base-content">
Storage Location
</label>
<.input
field={@form[:storage_location_id]}
type="select"
@@ -747,18 +790,25 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<%= if @editing_component && @editing_component.image_filename do %>
<div class="mt-1 mb-2">
<p class="text-sm text-base-content/70">Current image:</p>
<img src={"/uploads/images/#{@editing_component.image_filename}"} alt="Current component" class="h-20 w-20 object-cover rounded-lg" />
<img
src={"/uploads/images/#{@editing_component.image_filename}"}
alt="Current component"
class="h-20 w-20 object-cover rounded-lg"
/>
</div>
<% end %>
<div class="mt-1">
<.live_file_input upload={@uploads.image} class="block w-full text-sm text-base-content file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-primary/10 file:text-primary hover:file:bg-primary/20" />
<.live_file_input
upload={@uploads.image}
class="block w-full text-sm text-base-content file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-primary/10 file:text-primary hover:file:bg-primary/20"
/>
</div>
<p class="mt-1 text-xs text-base-content/60">
JPG, PNG, GIF up to 5MB (leave empty to keep current image)
</p>
<%= for err <- upload_errors(@uploads.image) do %>
<p class="text-error text-sm mt-1"><%= Phoenix.Naming.humanize(err) %></p>
<p class="text-error text-sm mt-1">{Phoenix.Naming.humanize(err)}</p>
<% end %>
<%= for entry <- @uploads.image.entries do %>
@@ -768,17 +818,23 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<.live_img_preview entry={entry} class="h-10 w-10 rounded-lg object-cover" />
</div>
<div class="ml-3">
<p class="text-sm font-medium text-base-content"><%= entry.client_name %></p>
<p class="text-sm text-base-content/60"><%= entry.progress %>%</p>
<p class="text-sm font-medium text-base-content">{entry.client_name}</p>
<p class="text-sm text-base-content/60">{entry.progress}%</p>
</div>
</div>
<button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref} aria-label="cancel" class="text-error hover:text-error/80">
<button
type="button"
phx-click="cancel-upload"
phx-value-ref={entry.ref}
aria-label="cancel"
class="text-error hover:text-error/80"
>
<.icon name="hero-x-mark" class="w-5 h-5" />
</button>
</div>
<% end %>
<%= for err <- upload_errors(@uploads.image) do %>
<p class="mt-1 text-sm text-error"><%= upload_error_to_string(err) %></p>
<p class="mt-1 text-sm text-error">{upload_error_to_string(err)}</p>
<% end %>
</div>
@@ -802,17 +858,28 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</div>
</div>
<% end %>
<!-- Components List -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-6">
<div class="bg-base-100 shadow overflow-hidden sm:rounded-md">
<ul class="divide-y divide-base-300" id="components-list" phx-update="replace">
<%= for component <- @components do %>
<li id={"component-#{component.id}"} class={[
"px-6 py-6 hover:bg-base-200 transition-all duration-200",
if(@focused_component_id == component.id, do: "bg-base-50 border-l-4 border-primary", else: "cursor-pointer"),
if(@interacting_with == to_string(component.id), do: "ring-2 ring-yellow-400 ring-opacity-50 bg-yellow-50", else: "")
]} phx-click={if @focused_component_id != component.id, do: "toggle_focus", else: nil} phx-value-id={component.id}>
<li
id={"component-#{component.id}"}
class={[
"px-6 py-6 hover:bg-base-200 transition-all duration-200",
if(@focused_component_id == component.id,
do: "bg-base-50 border-l-4 border-primary",
else: "cursor-pointer"
),
if(@interacting_with == to_string(component.id),
do: "ring-2 ring-yellow-400 ring-opacity-50 bg-yellow-50",
else: ""
)
]}
phx-click={if @focused_component_id != component.id, do: "toggle_focus", else: nil}
phx-value-id={component.id}
>
<%= if @focused_component_id == component.id do %>
<!-- Expanded/Focused View -->
<div class="space-y-6">
@@ -851,8 +918,8 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</button>
</div>
</div>
<!-- Content area with image and details -->
<!-- Content area with image and details -->
<div class="flex gap-6">
<!-- Large Image -->
<div class="flex-shrink-0">
@@ -870,12 +937,15 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</button>
<% else %>
<div class="h-48 w-48 rounded-lg bg-base-200 flex items-center justify-center border border-base-300">
<.icon name="hero-cube-transparent" class="h-20 w-20 text-base-content/50" />
<.icon
name="hero-cube-transparent"
class="h-20 w-20 text-base-content/50"
/>
</div>
<% end %>
</div>
<!-- Details -->
<!-- Details -->
<div class="flex-1 space-y-4 select-text">
<!-- Full Description -->
<%= if component.description do %>
@@ -886,15 +956,20 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</p>
</div>
<% end %>
<!-- Metadata Grid -->
<!-- Metadata Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<%= if component.storage_location do %>
<div class="flex items-start">
<.icon name="hero-map-pin" class="w-4 h-4 mr-2 text-base-content/50 mt-0.5 flex-shrink-0" />
<.icon
name="hero-map-pin"
class="w-4 h-4 mr-2 text-base-content/50 mt-0.5 flex-shrink-0"
/>
<div>
<span class="font-medium text-base-content">Location:</span>
<div class="text-base-content/70">{storage_location_display_name(component.storage_location)}</div>
<div class="text-base-content/70">
{storage_location_display_name(component.storage_location)}
</div>
</div>
</div>
<% end %>
@@ -906,10 +981,15 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</div>
<div class="flex items-start">
<.icon name="hero-calendar" class="w-4 h-4 mr-2 text-base-content/50 mt-0.5 flex-shrink-0" />
<.icon
name="hero-calendar"
class="w-4 h-4 mr-2 text-base-content/50 mt-0.5 flex-shrink-0"
/>
<div>
<span class="font-medium text-base-content">Entry Date:</span>
<div class="text-base-content/70">{Calendar.strftime(component.inserted_at, "%B %d, %Y")}</div>
<div class="text-base-content/70">
{Calendar.strftime(component.inserted_at, "%B %d, %Y")}
</div>
</div>
</div>
@@ -933,32 +1013,29 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</div>
</div>
</div>
<!-- Action Buttons -->
<!-- Action Buttons -->
<div class="flex justify-end items-center space-x-2 pt-4 border-t border-base-300">
<button
phx-click="increment_count"
phx-value-id={component.id}
class="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
>
<.icon name="hero-plus" class="w-4 h-4 mr-1" />
Add
<.icon name="hero-plus" class="w-4 h-4 mr-1" /> Add
</button>
<button
phx-click="decrement_count"
phx-value-id={component.id}
class="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-yellow-600 hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500"
>
<.icon name="hero-minus" class="w-4 h-4 mr-1" />
Remove
<.icon name="hero-minus" class="w-4 h-4 mr-1" /> Remove
</button>
<button
phx-click="show_edit_form"
phx-value-id={component.id}
class="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<.icon name="hero-pencil" class="w-4 h-4 mr-1" />
Edit
<.icon name="hero-pencil" class="w-4 h-4 mr-1" /> Edit
</button>
<button
phx-click="delete_component"
@@ -966,8 +1043,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
data-confirm="Are you sure you want to delete this component?"
class="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
<.icon name="hero-trash" class="w-4 h-4 mr-1" />
Delete
<.icon name="hero-trash" class="w-4 h-4 mr-1" /> Delete
</button>
</div>
</div>
@@ -982,7 +1058,11 @@ defmodule ComponentsElixirWeb.ComponentsLive do
phx-value-url={"/user_generated/uploads/images/#{component.image_filename}"}
class="hover:opacity-75 transition-opacity block"
>
<img src={"/user_generated/uploads/images/#{component.image_filename}"} alt={component.name} class="max-h-20 max-w-20 rounded-md object-contain cursor-pointer block" />
<img
src={"/user_generated/uploads/images/#{component.image_filename}"}
alt={component.name}
class="max-h-20 max-w-20 rounded-md object-contain cursor-pointer block"
/>
</button>
<% else %>
<div class="h-20 w-20 rounded-md bg-base-200 flex items-center justify-center">
@@ -1018,8 +1098,8 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</span>
</div>
</div>
<!-- Middle row: Description -->
<!-- Middle row: Description -->
<%= if component.description do %>
<div class="mt-1">
<p class="text-sm text-base-content/70 line-clamp-2">
@@ -1027,14 +1107,19 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</p>
</div>
<% end %>
<!-- Bottom row: Metadata -->
<!-- Bottom row: Metadata -->
<div class="mt-2 grid grid-cols-1 sm:grid-cols-3 gap-x-4 gap-y-1 text-sm text-base-content/60">
<%= if component.storage_location do %>
<div class="flex items-center min-w-0">
<.icon name="hero-map-pin" class="w-4 h-4 mr-1 text-base-content/50 flex-shrink-0" />
<.icon
name="hero-map-pin"
class="w-4 h-4 mr-1 text-base-content/50 flex-shrink-0"
/>
<span class="font-medium">Location:</span>
<span class="ml-1 truncate">{storage_location_display_name(component.storage_location)}</span>
<span class="ml-1 truncate">
{storage_location_display_name(component.storage_location)}
</span>
</div>
<% end %>
<div class="flex items-center">
@@ -1050,8 +1135,8 @@ defmodule ComponentsElixirWeb.ComponentsLive do
</div>
<% end %>
</div>
<!-- Keywords row -->
<!-- Keywords row -->
<%= if component.keywords do %>
<div class="mt-2">
<p class="text-xs text-base-content/50">
@@ -1127,12 +1212,18 @@ defmodule ComponentsElixirWeb.ComponentsLive do
<!-- Image Modal -->
<%= if @show_image_modal do %>
<div class="fixed inset-0 z-50 flex items-center justify-center p-4" phx-click="close_image_modal">
<div
class="fixed inset-0 z-50 flex items-center justify-center p-4"
phx-click="close_image_modal"
>
<!-- Background overlay -->
<div class="absolute inset-0 bg-black bg-opacity-75"></div>
<!-- Modal content -->
<div class="relative bg-base-100 rounded-lg shadow-xl max-w-4xl w-full max-h-full overflow-auto" phx-click="prevent_close">
<!-- Modal content -->
<div
class="relative bg-base-100 rounded-lg shadow-xl max-w-4xl w-full max-h-full overflow-auto"
phx-click="prevent_close"
>
<!-- Header -->
<div class="flex justify-between items-center p-4 border-b border-base-300 bg-base-100 rounded-t-lg">
<h3 class="text-lg font-semibold text-base-content">Component Image</h3>
@@ -1145,8 +1236,8 @@ defmodule ComponentsElixirWeb.ComponentsLive do
×
</button>
</div>
<!-- Content -->
<!-- Content -->
<div class="p-6 bg-base-100 rounded-b-lg">
<div class="text-center">
<%= if @modal_image_url do %>
@@ -1192,6 +1283,7 @@ defmodule ComponentsElixirWeb.ComponentsLive do
:ok ->
IO.puts("=== DEBUG: File copy successful ===")
{:ok, filename}
{:error, reason} ->
IO.puts("=== DEBUG: File copy failed: #{inspect(reason)} ===")
{:postpone, {:error, reason}}
@@ -1200,18 +1292,21 @@ defmodule ComponentsElixirWeb.ComponentsLive do
IO.inspect(uploaded_files, label: "Uploaded files result")
result = case uploaded_files do
[filename] when is_binary(filename) ->
IO.puts("=== DEBUG: Adding filename to params: #{filename} ===")
Map.put(component_params, "image_filename", filename)
[] ->
IO.puts("=== DEBUG: No files uploaded ===")
component_params
_error ->
IO.puts("=== DEBUG: Upload error ===")
IO.inspect(uploaded_files, label: "Unexpected upload result")
component_params
end
result =
case uploaded_files do
[filename] when is_binary(filename) ->
IO.puts("=== DEBUG: Adding filename to params: #{filename} ===")
Map.put(component_params, "image_filename", filename)
[] ->
IO.puts("=== DEBUG: No files uploaded ===")
component_params
_error ->
IO.puts("=== DEBUG: Upload error ===")
IO.inspect(uploaded_files, label: "Unexpected upload result")
component_params
end
IO.inspect(result, label: "Final component_params")
IO.puts("=== DEBUG: End save_uploaded_image ===")