Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 139 additions & 31 deletions app/admin/dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,169 @@
if ProgramSetting.active_program.exists?
active_program = ProgramSetting.active_program.last

# Get sort and pagination parameters
sort_column = params[:sort_column] || 'total_paid'
sort_order = params[:sort_order] || 'desc'
page = (params[:page] || 1).to_i
per_page = 20

# User Payment Totals Section
user_totals = Payment.current_program_payments(active_program.program_year)
.where(transaction_status: '1') # Only successful payments
.joins(:user)
.group('users.id', 'users.email')
.sum('payments.total_amount::float / 100')
.sort_by { |_, amount| -amount } # Sort by amount descending

# Calculate balance due for each user and prepare data for sorting
user_data_with_balance = user_totals.map do |user_data, total_paid|
user_id, user_email = user_data
user = User.find(user_id)
balance_due = Payment.current_balance_due_for_user(user, active_program.program_year)
{
user_id: user_id,
user_email: user_email,
user: user,
total_paid: total_paid,
balance_due: balance_due
}
end

# Apply sorting
user_data_with_balance = case sort_column
when 'user'
user_data_with_balance.sort_by { |data| data[:user_email] }
when 'balance_due'
user_data_with_balance.sort_by { |data| data[:balance_due].to_f }
else # 'total_paid' or default
user_data_with_balance.sort_by { |data| data[:total_paid] }
end

# Reverse if descending
user_data_with_balance.reverse! if sort_order == 'desc'

# Pagination
total_users = user_data_with_balance.count
total_pages = (total_users.to_f / per_page).ceil
page = [[page, 1].max, total_pages].min if total_pages > 0 # Ensure page is within valid range
start_index = (page - 1) * per_page
end_index = start_index + per_page - 1
paginated_data = user_data_with_balance[start_index..end_index] || []

div class: 'dashboard_section' do
h2 "User Payment Totals - Program Year #{active_program.program_year}"

if user_totals.any?
if user_data_with_balance.any?
table class: 'index_table' do
thead do
tr do
th 'User'
th do
next_order = (sort_column == 'user' && sort_order == 'asc') ? 'desc' : 'asc'
a href: admin_dashboard_path(sort_column: 'user', sort_order: next_order, page: page), title: 'Click to sort' do
text_node 'User '
span class: 'sort_indicator' do
if sort_column == 'user'
text_node sort_order == 'asc' ? '▲' : '▼'
else
text_node '⇅'
end
end
end
end
th 'Total Paid'
th 'Program Cost'
th 'Balance Due'
th do
next_order = (sort_column == 'balance_due' && sort_order == 'asc') ? 'desc' : 'asc'
a href: admin_dashboard_path(sort_column: 'balance_due', sort_order: next_order, page: page), title: 'Click to sort' do
text_node 'Balance Due '
span class: 'sort_indicator' do
if sort_column == 'balance_due'
text_node sort_order == 'asc' ? '▲' : '▼'
else
text_node '⇅'
end
end
end
end
th 'Status'
end
end
tbody do
user_totals.each do |user_data, total_paid|
user_id, user_email = user_data
user = User.find(user_id)
balance_due = Payment.current_balance_due_for_user(user, active_program.program_year)
status = balance_due.to_i.zero? ? 'Paid in Full' : 'Outstanding Balance'
status_class = balance_due.to_i.zero? ? 'paid_full' : 'outstanding'
paginated_data.each do |data|
status = data[:balance_due].to_i.zero? ? 'Paid in Full' : 'Outstanding Balance'
status_class = data[:balance_due].to_i.zero? ? 'paid_full' : 'outstanding'

tr class: status_class do
td user_email
td number_to_currency(total_paid)
td do
link_to data[:user_email], admin_payments_path('q[user_id_eq]' => data[:user_id]),
title: "View all payments for #{data[:user_email]}"
end
td number_to_currency(data[:total_paid])
td number_to_currency(active_program.total_cost)
td number_to_currency(balance_due)
td number_to_currency(data[:balance_due])
td status
end
end
end
end

div class: 'pagination_info' do
total_users = user_totals.count
paid_in_full = user_totals.count { |user_data, _|
user_id = user_data[0]
user = User.find(user_id)
Payment.current_balance_due_for_user(user, active_program.program_year).to_i.zero?
}
text_node "Total Users: #{total_users} | Paid in Full: #{paid_in_full} | Outstanding: #{total_users - paid_in_full}"
paid_in_full = user_data_with_balance.count { |data| data[:balance_due].to_i.zero? }
showing_start = total_users.zero? ? 0 : start_index + 1
showing_end = [end_index + 1, total_users].min
text_node "Displaying users #{showing_start} - #{showing_end} of #{total_users} | "
text_node "Paid in Full: #{paid_in_full} | Outstanding: #{total_users - paid_in_full}"
end

# Pagination controls
if total_pages > 1
div class: 'pagination', style: 'text-align: center; margin: 20px 0;' do
# Previous button
if page > 1
a href: admin_dashboard_path(sort_column: sort_column, sort_order: sort_order, page: page - 1),
class: 'pagination_link',
style: 'padding: 5px 10px; margin: 0 2px; text-decoration: none;' do
text_node '« Previous'
end
else
span style: 'padding: 5px 10px; margin: 0 2px; color: #ccc;' do
text_node '« Previous'
end
end

# Page numbers
(1..total_pages).each do |p|
if p == page
span class: 'current',
style: 'padding: 5px 10px; margin: 0 2px; background-color: #5E6469; color: white; border-radius: 3px;' do
text_node p.to_s
end
else
a href: admin_dashboard_path(sort_column: sort_column, sort_order: sort_order, page: p),
class: 'pagination_link',
style: 'padding: 5px 10px; margin: 0 2px; text-decoration: none;' do
text_node p.to_s
end
end
end

# Next button
if page < total_pages
a href: admin_dashboard_path(sort_column: sort_column, sort_order: sort_order, page: page + 1),
class: 'pagination_link',
style: 'padding: 5px 10px; margin: 0 2px; text-decoration: none;' do
text_node 'Next »'
end
else
span style: 'padding: 5px 10px; margin: 0 2px; color: #ccc;' do
text_node 'Next »'
end
end
end
end

# Section separator with subtle styling
div style: 'width: 100%; height: 1px; background-color: #e2e8f0; margin: 3rem 0 2rem 0; border-radius: 1px; box-shadow: 0 1px 3px rgba(0,0,0,0.08);' do
text_node ''
end
else
div class: 'blank_slate' do
Expand All @@ -64,11 +178,6 @@
end
end

# Hard line separator
div style: 'width: 100%; height: 4px; background-color: #2d3748; margin: 2rem 0; border: none;' do
text_node ''
end

# Recent Payments Section
recent_payments = Payment.current_program_payments(active_program.program_year)
.includes(:user)
Expand All @@ -94,7 +203,10 @@
tbody do
recent_payments.each do |payment|
tr do
td payment.user.email
td do
link_to payment.user.email, admin_payments_path('q[user_id_eq]' => payment.user_id),
title: "View all payments for #{payment.user.email}"
end
td payment.transaction_id
td number_to_currency(payment.total_amount.to_f / 100)
td payment.transaction_status == '1' ? 'Success' : 'Failed'
Expand All @@ -109,7 +221,7 @@
end

div class: 'pagination_info' do
text_node "Showing #{recent_payments.count} of #{Payment.current_program_payments(active_program.program_year).count} payments"
text_node "Showing most recent #{recent_payments.count} payments"
end
else
div class: 'blank_slate' do
Expand All @@ -125,10 +237,6 @@
end
end

# Hard line separator
div style: 'width: 100%; height: 4px; background-color: #2d3748; margin: 2rem 0; border: none;' do
text_node ''
end
# Static page message
text_node StaticPage.find_by(location: 'dashboard').message if StaticPage.find_by(location: 'dashboard').present?
end
Expand Down
10 changes: 8 additions & 2 deletions app/admin/payments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
# end
actions :index, :show, :new, :create, :update, :edit

filter :user_id, as: :select, collection: -> { User.all.map { |u| [u.email, u.id] } }
controller do
def scoped_collection
super.includes(:user)
end
end

filter :user_id, as: :select, collection: -> { User.order(:email).map { |u| [u.email, u.id] } }
filter :program_year, as: :select
filter :account_type, as: :select
filter :created_at
Expand Down Expand Up @@ -46,7 +52,7 @@
end

index do
column :user
column :user, sortable: 'users.email'
column 'Type', &:transaction_type
column 'Status', &:transaction_status
column :transaction_id
Expand Down
140 changes: 140 additions & 0 deletions lib/tasks/generate_sample_payments.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# frozen_string_literal: true

namespace :sample_data do
desc "Generate sample payment records for testing"
task generate_payments: :environment do
unless Rails.env.development?
puts "This task can only be run in development environment"
exit
end

puts "Generating sample payment data..."

# Get or create a program setting
program = ProgramSetting.find_or_create_by!(program_year: 2024) do |p|
p.active = true
p.program_open = Time.zone.now
p.program_close = 2.days.from_now
p.application_fee = 5000 # $50.00
p.program_fee = 25000 # $250.00
p.allow_payments = true
p.open_instructions = 'Open Instructions'
p.close_instructions = 'Close Instructions'
p.payment_instructions = 'Payment Instructions'
end

# Sample last names for more realistic data
last_names = %w[
Smith Johnson Williams Brown Jones Garcia Miller Davis Rodriguez Martinez
Hernandez Lopez Gonzalez Wilson Anderson Thomas Taylor Moore Jackson Martin
Lee Perez Thompson White Harris Sanchez Clark Ramirez Lewis Robinson Walker
Young Allen King Wright Scott Torres Nguyen Hill Flores Green Adams Nelson
Baker Hall Rivera Campbell Mitchell Carter Roberts Gomez Phillips Evans Turner
Diaz Parker Cruz Edwards Collins Reyes Stewart Morris Morales Murphy Cook
Rogers Gutierrez Ortiz Morgan Cooper Peterson Bailey Reed Kelly Howard Ramos
Kim Cox Ward Richardson Watson Brooks Chavez Wood James Bennett Gray Mendoza
Ruiz Hughes Price Alvarez Castillo Sanders Patel Myers Long Ross Foster Jimenez
]

first_names = %w[
James Mary Michael Patricia Robert Jennifer John Linda William Elizabeth
David Barbara Richard Susan Joseph Jessica Thomas Sarah Charles Karen
Christopher Nancy Daniel Lisa Matthew Betty Donald Ashley Mark Kimberly
Paul Emily Donald Donna George Carol Kenneth Michelle Steven Laura
Edward Sandra Brian Dorothy Ronald Ashley Anthony Melissa Kevin Amanda
Jason Stephanie Jeff Rebecca Ryan Deborah Gary Sharon Nicholas Laura
Jacob Cynthia Tyler Amy Scott Angela Eric Kathleen Stephen Shirley
Jonathan Emma Brandon Donna William Ruth Frank Anna Raymond Diana
]

transaction_statuses = ['1', '2', '3'] # 1=success, 2=pending, 3=failed
account_types = %w[VISA MASTERCARD AMEX DISCOVER]

# Keep track of created users to avoid duplicates
created_count = 0
target_count = 100

puts "Creating users and payments..."

target_count.times do |i|
# Generate unique email
first_name = first_names.sample
last_name = last_names.sample
email = "#{first_name.downcase}.#{last_name.downcase}.#{i}@example.com"

# Create user
user = User.create!(
email: email,
password: 'password123',
password_confirmation: 'password123'
)

# Generate 1-3 payments per user to make it more realistic
payment_count = rand(1..3)

payment_count.times do |payment_num|
status = transaction_statuses.sample
amount = case payment_num
when 0
program.application_fee # First payment is application fee
when 1
program.program_fee # Second payment is program fee
else
rand(5000..30000) # Additional payments vary
end

Payment.create!(
user: user,
transaction_type: '1',
transaction_status: status,
transaction_id: "TXN#{Time.current.to_i}#{rand(1000..9999)}",
total_amount: amount.to_s,
transaction_date: rand(30.days.ago..Time.current).strftime('%Y%m%d%H%M'),
account_type: account_types.sample,
result_code: status == '1' ? '00' : rand(100..999).to_s,
result_message: status == '1' ? 'Success' : 'Processing',
user_account: "**** **** **** #{rand(1000..9999)}",
payer_identity: email,
timestamp: rand(30.days.ago..Time.current).to_i.to_s,
transaction_hash: Digest::SHA256.hexdigest("#{email}#{Time.current.to_i}#{rand}"),
program_year: 2024
)

created_count += 1
end

print "\rCreated #{i + 1}/#{target_count} users with #{created_count} payments..."
end

puts "\n"
puts "✓ Successfully created #{target_count} users"
puts "✓ Successfully created #{created_count} payments"
puts "✓ All records are associated with program year 2024"
puts "\nYou can now view them in ActiveAdmin at /admin/payments"
end

desc "Clean up sample payment data"
task cleanup_payments: :environment do
unless Rails.env.development?
puts "This task can only be run in development environment"
exit
end

print "Are you sure you want to delete all payments and users from example.com? (yes/no): "
confirmation = STDIN.gets.chomp

if confirmation.downcase == 'yes'
users = User.where("email LIKE ?", "%@example.com")
payment_count = Payment.where(user: users).count
user_count = users.count

Payment.where(user: users).destroy_all
users.destroy_all

puts "✓ Deleted #{payment_count} payments"
puts "✓ Deleted #{user_count} users"
else
puts "Cleanup cancelled"
end
end
end