Skip to content

Commit eb9e75a

Browse files
authored
Merge pull request #980 from rubyforgood/issue-944_edit-portfolio-from-student
Issue 944 edit portfolio from student
2 parents 10b9b02 + c9b71e8 commit eb9e75a

File tree

8 files changed

+296
-18
lines changed

8 files changed

+296
-18
lines changed

app/controllers/admin_v2/students_controller.rb

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# frozen_string_literal: true
22

33
module AdminV2
4+
# rubocop:disable Metrics/ClassLength
45
class StudentsController < BaseController
56
include SoftDeletableFiltering
67

7-
before_action :set_student, only: %i[show edit update destroy]
8+
before_action :set_student, only: %i[show edit update destroy add_transaction]
89
before_action :set_discarded_student, only: %i[restore]
910

1011
def index
@@ -88,6 +89,28 @@ def restore
8889
redirect_to admin_v2_students_path(discarded: true), notice: t(".notice", username: username)
8990
end
9091

92+
def add_transaction
93+
errors = validate_transaction_params
94+
95+
if errors.present?
96+
redirect_to edit_admin_v2_student_path(@student), alert: errors.join(", ")
97+
else
98+
transaction = PortfolioTransaction.new(
99+
portfolio: @student.portfolio,
100+
amount_cents: transaction_amount_cents,
101+
transaction_type: transaction_type,
102+
reason: transaction_reason,
103+
description: transaction_description
104+
)
105+
106+
if transaction.save
107+
redirect_to admin_v2_student_path(@student), notice: t(".notice")
108+
else
109+
redirect_to edit_admin_v2_student_path(@student), alert: transaction.errors.full_messages.join(", ")
110+
end
111+
end
112+
end
113+
91114
private
92115

93116
def set_discarded_student
@@ -101,5 +124,35 @@ def set_student
101124
def student_params
102125
params.expect(student: %i[username classroom_id password password_confirmation])
103126
end
127+
128+
def transaction_params
129+
params.expect(student: %i[add_fund_amount transaction_type transaction_reason transaction_description])
130+
end
131+
132+
def transaction_amount_cents
133+
amount = transaction_params[:add_fund_amount]
134+
amount.present? ? (amount.to_f * 100).to_i : nil
135+
end
136+
137+
def transaction_type
138+
transaction_params[:transaction_type]
139+
end
140+
141+
def transaction_reason
142+
transaction_params[:transaction_reason]
143+
end
144+
145+
def transaction_description
146+
transaction_params[:transaction_description]
147+
end
148+
149+
def validate_transaction_params
150+
errors = []
151+
errors << t("admin_v2.students.add_transaction.errors.transaction_type_blank") if transaction_type.blank?
152+
errors << t("admin_v2.students.add_transaction.errors.amount_blank") if transaction_amount_cents.blank?
153+
errors << t("admin_v2.students.add_transaction.errors.reason_blank") if transaction_reason.blank?
154+
errors
155+
end
104156
end
157+
# rubocop:enable Metrics/ClassLength
105158
end

app/helpers/admin_v2_helper.rb

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,7 @@ def format_attribute(resource, attribute)
5151
when Time, DateTime, Date
5252
value.strftime("%B %d, %Y")
5353
when ActiveRecord::Base
54-
# Try to link to the resource, but fall back to text if route doesn't exist
55-
begin
56-
link_to value.to_s, [:admin_v2, route_model(value)]
57-
rescue NoMethodError, ActionController::UrlGenerationError
58-
value.to_s
59-
end
54+
format_association(value)
6055
when nil
6156
content_tag(:span, "—", class: "text-gray-400")
6257
else
@@ -160,8 +155,39 @@ def discard_restore_action(resource)
160155
end
161156
end
162157

158+
# Returns the appropriate show path for a user based on their type
159+
# @param user [User] The user record
160+
# @return [String] Path to the type-specific show page
161+
def user_show_path(user)
162+
case user.type
163+
when "Student"
164+
admin_v2_student_path(user)
165+
when "Teacher"
166+
admin_v2_teacher_path(user)
167+
else
168+
admin_v2_user_path(user)
169+
end
170+
end
171+
163172
private
164173

174+
def format_association(value)
175+
# Use presenter if available, otherwise fall back to to_s
176+
presenter_class = "#{value.class.name}Presenter".safe_constantize
177+
display_value = if presenter_class
178+
presenter_class.new(value).display_name
179+
else
180+
value.to_s
181+
end
182+
183+
# Try to link to the resource, but fall back to text if route doesn't exist
184+
begin
185+
link_to display_value, [:admin_v2, route_model(value)]
186+
rescue NoMethodError, ActionController::UrlGenerationError
187+
display_value
188+
end
189+
end
190+
165191
def restore_button(resource)
166192
resource_name = resource.class.name.underscore
167193
restore_path = send("restore_admin_v2_#{resource_name}_path", resource)

app/views/admin_v2/students/_form.html.erb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,49 @@
4343
<%= f.submit_button @student.persisted? ? "Update Student" : "Create Student" %>
4444
</div>
4545
<% end %>
46+
47+
<% if @student.persisted? %>
48+
<%= form_with url: add_transaction_admin_v2_student_path(@student), method: :post, builder: AdminV2::FormBuilder, class: "space-y-6 mt-6" do |f| %>
49+
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
50+
<div class="px-6 py-6">
51+
<h3 class="text-lg font-medium text-gray-900 mb-6">Add Portfolio Transaction</h3>
52+
53+
<!-- Current Cash Balance (Read-only) -->
54+
<div class="mb-6">
55+
<label class="block text-sm font-medium text-gray-700 mb-1">Current Cash Balance</label>
56+
<div class="text-lg font-semibold text-gray-900">
57+
<%= number_to_currency(@student.portfolio&.cash_balance || 0) %>
58+
</div>
59+
</div>
60+
61+
<%= f.select :transaction_type,
62+
[["", ""], ["Deposit", "deposit"], ["Debit", "debit"]],
63+
label: "Transaction Type",
64+
hint: "Select deposit to add funds or debit to remove funds",
65+
include_blank: false %>
66+
67+
<%= f.number_field :add_fund_amount,
68+
label: "Amount",
69+
hint: "Transaction amount in dollars",
70+
placeholder: "0.00",
71+
step: "0.01" %>
72+
73+
<%= f.select :transaction_reason,
74+
PortfolioTransaction.reasons.keys.map { |key| [PortfolioTransaction.human_attribute_name("reason.#{key}"), key] },
75+
label: "Reason",
76+
hint: "Reason for this transaction",
77+
include_blank: "Select a reason" %>
78+
79+
<%= f.text_area :transaction_description,
80+
label: "Description (Optional)",
81+
hint: "Additional details about this transaction",
82+
placeholder: "Enter transaction details...",
83+
rows: 3 %>
84+
</div>
85+
</div>
86+
87+
<div class="flex items-center justify-end gap-x-4">
88+
<%= f.submit_button "Add Transaction" %>
89+
</div>
90+
<% end %>
91+
<% end %>

app/views/admin_v2/students/show.html.erb

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,91 @@
2020
</div>
2121
</div>
2222

23-
<%= admin_show_attributes(
24-
@student,
25-
attributes: %i[id username classroom created_at updated_at]
26-
) %>
23+
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
24+
<div class="px-4 py-5 sm:px-6">
25+
<dl class="divide-y divide-gray-200">
26+
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
27+
<dt class="text-sm font-medium text-gray-500">Id</dt>
28+
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.id %></dd>
29+
</div>
30+
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
31+
<dt class="text-sm font-medium text-gray-500">Username</dt>
32+
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.username %></dd>
33+
</div>
34+
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
35+
<dt class="text-sm font-medium text-gray-500">Classroom</dt>
36+
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.classroom.name %></dd>
37+
</div>
38+
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
39+
<dt class="text-sm font-medium text-gray-500">Created at</dt>
40+
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.created_at.strftime("%B %d, %Y") %></dd>
41+
</div>
42+
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
43+
<dt class="text-sm font-medium text-gray-500">Updated at</dt>
44+
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.updated_at.strftime("%B %d, %Y") %></dd>
45+
</div>
46+
</dl>
47+
</div>
48+
</div>
2749

2850
<!-- Associated Portfolio -->
2951
<% if @student.portfolio.present? %>
3052
<div class="mt-6 pt-6 border-t border-gray-200">
31-
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio</h3>
32-
<div class="text-sm text-gray-900">
33-
Portfolio ID: <%= @student.portfolio.id %>
34-
<%= link_to "View Portfolio", user_portfolio_path(@student, @student.portfolio), class: "ml-2 text-blue-600 hover:text-blue-800" %>
53+
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio Details</h3>
54+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
55+
<div class="bg-blue-50 p-4 rounded-lg">
56+
<div class="text-xs font-medium text-blue-600 mb-1">Cash Balance</div>
57+
<div class="text-2xl font-bold text-blue-900"><%= number_to_currency(@student.portfolio.cash_balance) %></div>
58+
</div>
59+
<div class="bg-green-50 p-4 rounded-lg">
60+
<div class="text-xs font-medium text-green-600 mb-1">Total Portfolio Worth</div>
61+
<div class="text-2xl font-bold text-green-900"><%= number_to_currency(@student.portfolio.total_portfolio_worth) %></div>
62+
</div>
63+
<div class="bg-gray-50 p-4 rounded-lg">
64+
<div class="text-xs font-medium text-gray-600 mb-1">Portfolio ID</div>
65+
<div class="text-2xl font-bold text-gray-900">#<%= @student.portfolio.id %></div>
66+
</div>
3567
</div>
68+
69+
<!-- Transactions -->
70+
<% transactions = @student.portfolio.portfolio_transactions.order(created_at: :desc) %>
71+
<% if transactions.any? %>
72+
<h4 class="text-sm font-medium text-gray-700 mb-3">All Transactions (<%= transactions.count %>)</h4>
73+
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg mb-4">
74+
<table class="min-w-full divide-y divide-gray-300">
75+
<thead class="bg-gray-50">
76+
<tr>
77+
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
78+
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
79+
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Reason</th>
80+
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500 uppercase">Amount</th>
81+
</tr>
82+
</thead>
83+
<tbody class="divide-y divide-gray-200 bg-white">
84+
<% transactions.each do |transaction| %>
85+
<tr>
86+
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">
87+
<%= transaction.created_at.strftime("%m/%d/%Y") %>
88+
</td>
89+
<td class="whitespace-nowrap px-3 py-2 text-sm">
90+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= transaction.transaction_type == 'deposit' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' %>">
91+
<%= transaction.transaction_type.capitalize %>
92+
</span>
93+
</td>
94+
<td class="px-3 py-2 text-sm text-gray-900">
95+
<%= transaction.reason&.humanize || 'N/A' %>
96+
</td>
97+
<td class="whitespace-nowrap px-3 py-2 text-sm text-right font-medium text-gray-900">
98+
<%= number_to_currency(transaction.amount_cents / 100.0) %>
99+
</td>
100+
</tr>
101+
<% end %>
102+
</tbody>
103+
</table>
104+
</div>
105+
<% else %>
106+
<p class="text-sm text-gray-500 mb-4">No transactions yet.</p>
107+
<% end %>
36108
</div>
37109
<% else %>
38110
<div class="mt-6 pt-6 border-t border-gray-200">

app/views/admin_v2/users/index.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
</td>
6666
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
6767
<div class="flex justify-end gap-2">
68-
<%= link_to "View", admin_v2_user_path(user), class: "text-blue-600 hover:text-blue-800" %>
68+
<%= link_to "View", user_show_path(user), class: "text-blue-600 hover:text-blue-800" %>
6969
<%= link_to "Edit", edit_admin_v2_user_path(user), class: "text-gray-600 hover:text-gray-800" %>
7070
<%= link_to "Delete", admin_v2_user_path(user),
7171
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" },

config/locales/en.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ en:
7272
update:
7373
notice: Stock updated successfully.
7474
students:
75+
add_transaction:
76+
errors:
77+
amount_blank: Amount must be present
78+
reason_blank: Reason must be present
79+
transaction_type_blank: Transaction Type must be present
80+
notice: Transaction added successfully.
7581
create:
7682
notice: 'Student %{username} created successfully. Password: %{password}'
7783
destroy:

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
resources :students do
9292
member do
9393
patch :restore
94+
post :add_transaction
9495
end
9596
end
9697
resources :teachers

0 commit comments

Comments
 (0)