feat: datasheet upload and auto-retrieve
- store datasheet PDFs on the server - download PDF automatically when given a link
This commit is contained in:
@@ -47,6 +47,11 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
max_entries: 1,
|
||||
max_file_size: 5_000_000
|
||||
)
|
||||
|> allow_upload(:datasheet,
|
||||
accept: ~w(.pdf),
|
||||
max_entries: 1,
|
||||
max_file_size: 10_000_000
|
||||
)
|
||||
|> load_components()}
|
||||
end
|
||||
end
|
||||
@@ -350,11 +355,18 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
{:noreply, cancel_upload(socket, :image, ref)}
|
||||
end
|
||||
|
||||
def handle_event("save_component", %{"component" => component_params}, socket) do
|
||||
# Handle any uploaded images
|
||||
updated_params = save_uploaded_image(socket, component_params)
|
||||
def handle_event("cancel-datasheet-upload", %{"ref" => ref}, socket) do
|
||||
{:noreply, cancel_upload(socket, :datasheet, ref)}
|
||||
end
|
||||
|
||||
case Inventory.create_component(updated_params) do
|
||||
def handle_event("save_component", %{"component" => component_params}, socket) do
|
||||
# Handle any uploaded files
|
||||
updated_params =
|
||||
socket
|
||||
|> save_uploaded_image(component_params)
|
||||
|> save_uploaded_datasheet(socket)
|
||||
|
||||
case Inventory.create_component_with_datasheet(updated_params) do
|
||||
{:ok, _component} ->
|
||||
{:noreply,
|
||||
socket
|
||||
@@ -369,10 +381,13 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
end
|
||||
|
||||
def handle_event("save_edit", %{"component" => component_params}, socket) do
|
||||
# Handle any uploaded images
|
||||
updated_params = save_uploaded_image(socket, component_params)
|
||||
# Handle any uploaded files
|
||||
updated_params =
|
||||
socket
|
||||
|> save_uploaded_image(component_params)
|
||||
|> save_uploaded_datasheet(socket)
|
||||
|
||||
case Inventory.update_component(socket.assigns.editing_component, updated_params) do
|
||||
case Inventory.update_component_with_datasheet(socket.assigns.editing_component, updated_params) do
|
||||
{:ok, _component} ->
|
||||
{:noreply,
|
||||
socket
|
||||
@@ -804,6 +819,51 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-base-content">Datasheet Upload</label>
|
||||
<div class="mt-1">
|
||||
<.live_file_input
|
||||
upload={@uploads.datasheet}
|
||||
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-secondary/10 file:text-secondary hover:file:bg-secondary/20"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-base-content/60">
|
||||
PDF files up to 10MB (or enter URL above to auto-download)
|
||||
</p>
|
||||
|
||||
<%= for err <- upload_errors(@uploads.datasheet) do %>
|
||||
<p class="text-error text-sm mt-1">{Phoenix.Naming.humanize(err)}</p>
|
||||
<% end %>
|
||||
|
||||
<%= for entry <- @uploads.datasheet.entries do %>
|
||||
<div class="mt-2 flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="h-10 w-10 rounded-lg bg-secondary/10 flex items-center justify-center">
|
||||
<.icon name="hero-document-text" class="w-6 h-6 text-secondary" />
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="cancel-datasheet-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.datasheet) do %>
|
||||
<p class="mt-1 text-sm text-error">{upload_error_to_string(err)}</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
@@ -948,6 +1008,64 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-base-content">Datasheet</label>
|
||||
<%= if @editing_component && @editing_component.datasheet_filename do %>
|
||||
<div class="mt-1 mb-2">
|
||||
<p class="text-sm text-base-content/70">Current datasheet:</p>
|
||||
<a
|
||||
href={"/uploads/datasheets/#{@editing_component.datasheet_filename}"}
|
||||
target="_blank"
|
||||
class="inline-flex items-center text-primary hover:text-primary/80"
|
||||
>
|
||||
<.icon name="hero-document-text" class="w-4 h-4 mr-1" />
|
||||
View PDF
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="mt-1">
|
||||
<.live_file_input
|
||||
upload={@uploads.datasheet}
|
||||
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-secondary/10 file:text-secondary hover:file:bg-secondary/20"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-base-content/60">
|
||||
PDF files up to 10MB (leave empty to keep current, or enter URL above to auto-download)
|
||||
</p>
|
||||
|
||||
<%= for err <- upload_errors(@uploads.datasheet) do %>
|
||||
<p class="text-error text-sm mt-1">{Phoenix.Naming.humanize(err)}</p>
|
||||
<% end %>
|
||||
|
||||
<%= for entry <- @uploads.datasheet.entries do %>
|
||||
<div class="mt-2 flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="h-10 w-10 rounded-lg bg-secondary/10 flex items-center justify-center">
|
||||
<.icon name="hero-document-text" class="w-6 h-6 text-secondary" />
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="cancel-datasheet-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.datasheet) do %>
|
||||
<p class="mt-1 text-sm text-error">{upload_error_to_string(err)}</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
@@ -997,19 +1115,28 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center min-w-0 flex-1">
|
||||
<h3 class="text-lg font-semibold text-primary select-text">
|
||||
<%= if component.datasheet_url do %>
|
||||
<a
|
||||
href={component.datasheet_url}
|
||||
target="_blank"
|
||||
class="hover:text-primary/80"
|
||||
>
|
||||
<%= cond do %>
|
||||
<% component.datasheet_filename -> %>
|
||||
<a
|
||||
href={"/uploads/datasheets/#{component.datasheet_filename}"}
|
||||
target="_blank"
|
||||
class="hover:text-primary/80"
|
||||
>
|
||||
{component.name}
|
||||
</a>
|
||||
<% component.datasheet_url -> %>
|
||||
<a
|
||||
href={component.datasheet_url}
|
||||
target="_blank"
|
||||
class="hover:text-primary/80"
|
||||
>
|
||||
{component.name}
|
||||
</a>
|
||||
<% true -> %>
|
||||
{component.name}
|
||||
</a>
|
||||
<% else %>
|
||||
{component.name}
|
||||
<% end %>
|
||||
</h3>
|
||||
<%= if component.datasheet_url do %>
|
||||
<%= if component.datasheet_url || component.datasheet_filename do %>
|
||||
<span class="ml-2 text-primary" title="Datasheet available">📄</span>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1119,6 +1246,41 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if component.datasheet_filename || component.datasheet_url do %>
|
||||
<div class="flex items-start">
|
||||
<.icon name="hero-document-text" 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">Datasheet:</span>
|
||||
<div class="space-y-1 mt-1">
|
||||
<%= if component.datasheet_filename do %>
|
||||
<div>
|
||||
<a
|
||||
href={"/uploads/datasheets/#{component.datasheet_filename}"}
|
||||
target="_blank"
|
||||
class="inline-flex items-center text-primary hover:text-primary/80 text-sm"
|
||||
>
|
||||
<.icon name="hero-document-arrow-down" class="w-4 h-4 mr-1" />
|
||||
View PDF (Downloaded)
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= if component.datasheet_url do %>
|
||||
<div>
|
||||
<a
|
||||
href={component.datasheet_url}
|
||||
target="_blank"
|
||||
class="inline-flex items-center text-base-content/70 hover:text-primary text-sm"
|
||||
>
|
||||
<.icon name="hero-link" class="w-4 h-4 mr-1" />
|
||||
Original URL
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1185,19 +1347,28 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-primary truncate">
|
||||
<%= if component.datasheet_url do %>
|
||||
<a
|
||||
href={component.datasheet_url}
|
||||
target="_blank"
|
||||
class="hover:text-primary/80"
|
||||
>
|
||||
<%= cond do %>
|
||||
<% component.datasheet_filename -> %>
|
||||
<a
|
||||
href={"/uploads/datasheets/#{component.datasheet_filename}"}
|
||||
target="_blank"
|
||||
class="hover:text-primary/80"
|
||||
>
|
||||
{component.name}
|
||||
</a>
|
||||
<% component.datasheet_url -> %>
|
||||
<a
|
||||
href={component.datasheet_url}
|
||||
target="_blank"
|
||||
class="hover:text-primary/80"
|
||||
>
|
||||
{component.name}
|
||||
</a>
|
||||
<% true -> %>
|
||||
{component.name}
|
||||
</a>
|
||||
<% else %>
|
||||
{component.name}
|
||||
<% end %>
|
||||
</p>
|
||||
<%= if component.datasheet_url do %>
|
||||
<%= if component.datasheet_url || component.datasheet_filename do %>
|
||||
<span class="ml-2 text-primary" title="Datasheet available">📄</span>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1421,6 +1592,40 @@ defmodule ComponentsElixirWeb.ComponentsLive do
|
||||
result
|
||||
end
|
||||
|
||||
# Helper function for datasheet upload handling
|
||||
defp save_uploaded_datasheet(component_params, socket) do
|
||||
uploaded_files =
|
||||
consume_uploaded_entries(socket, :datasheet, fn %{path: path}, entry ->
|
||||
filename = "#{System.unique_integer([:positive])}_#{entry.client_name}"
|
||||
uploads_dir = Application.get_env(:components_elixir, :uploads_dir)
|
||||
upload_dir = Path.join([uploads_dir, "datasheets"])
|
||||
dest = Path.join(upload_dir, filename)
|
||||
|
||||
# Ensure the upload directory exists
|
||||
File.mkdir_p!(upload_dir)
|
||||
|
||||
# Copy the file
|
||||
case File.cp(path, dest) do
|
||||
:ok ->
|
||||
{:ok, filename}
|
||||
|
||||
{:error, reason} ->
|
||||
{:postpone, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
|
||||
case uploaded_files do
|
||||
[filename] when is_binary(filename) ->
|
||||
Map.put(component_params, "datasheet_filename", filename)
|
||||
|
||||
[] ->
|
||||
component_params
|
||||
|
||||
_error ->
|
||||
component_params
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_image_file(nil), do: :ok
|
||||
defp delete_image_file(""), do: :ok
|
||||
|
||||
|
||||
Reference in New Issue
Block a user