Initial commit: Phoenix LiveView demo for interactive data tables with filtering, sorting, pagination, URL state, and progressive enhancement
All checks were successful
build / build (push) Successful in 11s
All checks were successful
build / build (push) Successful in 11s
Implements a fully-featured action requests table in a single LiveView module using Flop, Ecto, and SQLite. Includes: - Fuzzy search, status/assignment filters, column sorting, 15-per-page pagination - Real-time updates, bookmarkable URLs via `handle_params/3` - JS-disabled fallback with GET forms (no duplicate logic) - 1,000,000 seeded records, Tailwind + DaisyUI styling, light/dark themes - Comprehensive README with comparisons to Django+React/Rails+React stacks - 31 tests covering all scenarios Tech: Phoenix 1.8+, LiveView, Flop, Ecto, SQLite, Elixir 1.15+
This commit is contained in:
4
priv/repo/migrations/.formatter.exs
Normal file
4
priv/repo/migrations/.formatter.exs
Normal file
@@ -0,0 +1,4 @@
|
||||
[
|
||||
import_deps: [:ecto_sql],
|
||||
inputs: ["*.exs"]
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
defmodule ActionRequestsDemo.Repo.Migrations.CreateActionRequests do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:action_requests, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :patient_name, :string
|
||||
add :status, :string
|
||||
add :assigned_user_id, :integer
|
||||
add :delivery_scheduled_at, :naive_datetime
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create index(:action_requests, [:patient_name])
|
||||
create index(:action_requests, [:status])
|
||||
create index(:action_requests, [:assigned_user_id])
|
||||
create index(:action_requests, [:inserted_at])
|
||||
end
|
||||
end
|
||||
89
priv/repo/seeds.exs
Normal file
89
priv/repo/seeds.exs
Normal file
@@ -0,0 +1,89 @@
|
||||
# Script for populating the database. You can run it as:
|
||||
#
|
||||
# mix run priv/repo/seeds.exs
|
||||
#
|
||||
# Inside the script, you can read and write to any of your
|
||||
# repositories directly:
|
||||
#
|
||||
# ActionRequestsDemo.Repo.insert!(%ActionRequestsDemo.SomeSchema{})
|
||||
#
|
||||
# We recommend using the bang functions (`insert!`, `update!`
|
||||
# and so on) as they will fail if something goes wrong.
|
||||
|
||||
alias ActionRequestsDemo.Repo
|
||||
alias ActionRequestsDemo.ActionRequests.ActionRequest
|
||||
|
||||
# Configuration
|
||||
total_records = 1_000_000
|
||||
# SQLite has a max parameter limit of 32766
|
||||
# With 7 fields per record, max is ~4681 records per batch
|
||||
# Using 4000 to be safe
|
||||
batch_size = 4_000
|
||||
num_batches = div(total_records, batch_size)
|
||||
|
||||
IO.puts("Seeding #{total_records} action requests in #{num_batches} batches of #{batch_size}...")
|
||||
IO.puts("Started at: #{DateTime.utc_now()}")
|
||||
|
||||
statuses = ["resolved", "unresolved"]
|
||||
|
||||
# Helper function to generate a single record
|
||||
generate_record = fn ->
|
||||
patient_name = Faker.Person.name()
|
||||
status = Enum.random(statuses)
|
||||
assigned_user_id = if :rand.uniform() > 0.3, do: Enum.random(1..5), else: nil
|
||||
days_ago = :rand.uniform(365)
|
||||
|
||||
inserted_at =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(-days_ago * 24 * 3600, :second)
|
||||
|> DateTime.truncate(:second)
|
||||
|
||||
delivery_scheduled_at =
|
||||
if :rand.uniform() > 0.4 do
|
||||
days_ahead = :rand.uniform(14)
|
||||
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(days_ahead * 24 * 3600, :second)
|
||||
|> NaiveDateTime.truncate(:second)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
%{
|
||||
id: Ecto.UUID.generate(),
|
||||
patient_name: patient_name,
|
||||
status: status,
|
||||
assigned_user_id: assigned_user_id,
|
||||
delivery_scheduled_at: delivery_scheduled_at,
|
||||
inserted_at: inserted_at,
|
||||
updated_at: inserted_at
|
||||
}
|
||||
end
|
||||
|
||||
# Process batches sequentially (SQLite doesn't handle concurrent writes well)
|
||||
start_time = System.monotonic_time(:millisecond)
|
||||
|
||||
Enum.each(1..num_batches, fn batch_num ->
|
||||
# Generate batch of records
|
||||
records = for _i <- 1..batch_size, do: generate_record.()
|
||||
|
||||
# Insert batch in a single transaction
|
||||
{count, _} = Repo.insert_all(ActionRequest, records)
|
||||
|
||||
if rem(batch_num, 10) == 0 do
|
||||
elapsed = div(System.monotonic_time(:millisecond) - start_time, 1000)
|
||||
progress = batch_num * batch_size
|
||||
rate = if elapsed > 0, do: div(progress, elapsed), else: 0
|
||||
IO.puts("Progress: #{progress}/#{total_records} (#{div(progress * 100, total_records)}%) - #{rate} records/sec")
|
||||
end
|
||||
|
||||
count
|
||||
end)
|
||||
|
||||
end_time = System.monotonic_time(:millisecond)
|
||||
elapsed_seconds = div(end_time - start_time, 1000)
|
||||
records_per_second = div(total_records, max(elapsed_seconds, 1))
|
||||
|
||||
IO.puts("\n✓ Successfully seeded #{total_records} action requests!")
|
||||
IO.puts("Total time: #{elapsed_seconds} seconds (#{records_per_second} records/sec)")
|
||||
IO.puts("Finished at: #{DateTime.utc_now()}")
|
||||
BIN
priv/static/favicon.ico
Normal file
BIN
priv/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 152 B |
5
priv/static/robots.txt
Normal file
5
priv/static/robots.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
||||
#
|
||||
# To ban all spiders from the entire site uncomment the next two lines:
|
||||
# User-agent: *
|
||||
# Disallow: /
|
||||
Reference in New Issue
Block a user