chore: init commit
in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class AnnouncementsController < BaseController
|
||||
before_action :set_announcement, only: %i[show edit update destroy]
|
||||
|
||||
def index
|
||||
sort_column = params[:sort].presence || "created_at"
|
||||
sort_direction = params[:direction] == "desc" ? :desc : :asc
|
||||
|
||||
@announcements = Announcement.reorder(sort_column => sort_direction)
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Announcements" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@breadcrumbs = [
|
||||
{ label: "Announcements", path: admin_announcements_path },
|
||||
{ label: @announcement.title }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@announcement = Announcement.new
|
||||
@breadcrumbs = [
|
||||
{ label: "Announcements", path: admin_announcements_path },
|
||||
{ label: "New Announcement" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
@breadcrumbs = [
|
||||
{ label: "Announcements", path: admin_announcements_path },
|
||||
{ label: @announcement.title, path: admin_announcement_path(@announcement) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
@announcement = Announcement.new(announcement_params)
|
||||
|
||||
if @announcement.save
|
||||
redirect_to admin_announcement_path(@announcement), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Announcements", path: admin_announcements_path },
|
||||
{ label: "New Announcement" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @announcement.update(announcement_params)
|
||||
redirect_to admin_announcement_path(@announcement), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Announcements", path: admin_announcements_path },
|
||||
{ label: @announcement.title, path: admin_announcement_path(@announcement) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@announcement.destroy
|
||||
redirect_to admin_announcements_path, notice: t(".notice")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_announcement
|
||||
@announcement = Announcement.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def announcement_params
|
||||
params.expect(announcement: %i[title content featured])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Base controller for in-house admin.
|
||||
# All Admin controllers inherit from this controller.
|
||||
module Admin
|
||||
class BaseController < ApplicationController
|
||||
layout "admin"
|
||||
|
||||
before_action :authenticate_admin
|
||||
|
||||
private
|
||||
|
||||
def authenticate_admin
|
||||
redirect_to root_path, alert: t("application.access_denied.admin_required") unless current_user&.admin?
|
||||
end
|
||||
|
||||
# Apply sorting to a collection based on params
|
||||
# @param collection [ActiveRecord::Relation] The base collection to sort
|
||||
# @param default [String] The default column to sort by
|
||||
# @return [ActiveRecord::Relation] The sorted collection
|
||||
def apply_sorting(collection, default:)
|
||||
sort_column = params[:sort].presence || default
|
||||
sort_direction = params[:direction] == "desc" ? :desc : :asc
|
||||
collection.reorder(sort_column => sort_direction)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ClassroomsController < BaseController
|
||||
before_action :set_classroom, only: %i[show edit update toggle_archive]
|
||||
|
||||
def index
|
||||
@classrooms = apply_sorting(Classroom.all, default: "name")
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Classrooms" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@breadcrumbs = [
|
||||
{ label: "Classrooms", path: admin_classrooms_path },
|
||||
{ label: @classroom.name }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@classroom = Classroom.new
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Classrooms", path: admin_classrooms_path },
|
||||
{ label: "New Classroom" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
@breadcrumbs = [
|
||||
{ label: "Classrooms", path: admin_classrooms_path },
|
||||
{ label: @classroom.name, path: admin_classroom_path(@classroom) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
@classroom = Classroom.new(classroom_params)
|
||||
|
||||
if @classroom.save
|
||||
redirect_to admin_classroom_path(@classroom), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Classrooms", path: admin_classrooms_path },
|
||||
{ label: "New Classroom" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @classroom.update(classroom_params)
|
||||
redirect_to admin_classroom_path(@classroom), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Classrooms", path: admin_classrooms_path },
|
||||
{ label: @classroom.name, path: admin_classroom_path(@classroom) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_archive
|
||||
authorize @classroom, :toggle_archive?
|
||||
@classroom.update!(archived: !@classroom.archived)
|
||||
flash[:notice] = @classroom.archived? ? "Classroom has been archived." : "Classroom has been activated."
|
||||
redirect_to admin_classroom_path(@classroom)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_classroom
|
||||
@classroom = Classroom.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def classroom_params
|
||||
params.expect(classroom: [:name, :trading_enabled, :school_year_id, { grade_ids: [] }])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ComponentDemoController < BaseController
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def index
|
||||
# Demo data for table component - apply sorting before limit
|
||||
@users = User.all
|
||||
|
||||
# Apply sorting if params present
|
||||
if params[:sort].present?
|
||||
direction = params[:direction] == "desc" ? :desc : :asc
|
||||
@users = @users.order(params[:sort] => direction)
|
||||
end
|
||||
|
||||
@users = @users.limit(10)
|
||||
|
||||
# Demo data for show component
|
||||
@sample_user = User.first || User.new(
|
||||
email: "demo@example.com",
|
||||
name: "Demo User",
|
||||
admin: true,
|
||||
created_at: Time.current
|
||||
)
|
||||
|
||||
# Demo breadcrumbs
|
||||
@breadcrumbs = [
|
||||
{ label: "Components", path: admin_component_demo_index_path },
|
||||
{ label: "Demo" }
|
||||
]
|
||||
|
||||
# Demo table columns
|
||||
@columns = [
|
||||
{ attribute: :id, label: "ID", sortable: true },
|
||||
{ attribute: :email, label: "Email", sortable: true },
|
||||
{ attribute: :name, label: "Name", sortable: true },
|
||||
{ attribute: :admin, label: "Admin", sortable: true },
|
||||
{ attribute: :created_at, label: "Created", sortable: true }
|
||||
]
|
||||
|
||||
# Demo filters
|
||||
@filters = [
|
||||
{
|
||||
name: :type,
|
||||
label: "User Type",
|
||||
options: [["All", ""], ["Admin", "admin"], ["Teacher", "teacher"], ["Student", "student"]]
|
||||
},
|
||||
{
|
||||
name: :status,
|
||||
label: "Status",
|
||||
options: [["All", ""], ["Active", "active"], ["Inactive", "inactive"]]
|
||||
}
|
||||
]
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def show
|
||||
@user = User.find(params.expect(:id))
|
||||
@breadcrumbs = [
|
||||
{ label: "Components", path: admin_component_demo_index_path },
|
||||
{ label: "User Details" }
|
||||
]
|
||||
end
|
||||
|
||||
def form
|
||||
@user = User.first || User.new(email: "", name: "", admin: false)
|
||||
|
||||
# Add validation errors for demo purposes if requested
|
||||
if params[:show_errors] == "true"
|
||||
@user.errors.add(:email, "can't be blank")
|
||||
@user.errors.add(:name, "is too short (minimum is 3 characters)")
|
||||
end
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Components", path: admin_component_demo_index_path },
|
||||
{ label: "Form Builder Demo" }
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class DashboardController < BaseController
|
||||
def index
|
||||
# Dashboard will show overview of admin resources
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class PortfolioTransactionsController < BaseController
|
||||
before_action :set_portfolio_transaction, only: %i[show edit update destroy]
|
||||
|
||||
def show
|
||||
# TODO: FIX
|
||||
# authorize @portfolio_transaction
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Portfolio Transaction ##{@portfolio_transaction.id}" }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@portfolio_transaction = PortfolioTransaction.new
|
||||
# TODO: FIX
|
||||
# authorize @portfolio_transaction
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Portfolio Transactions", path: "#" },
|
||||
{ label: "New" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
# TODO: Determine if explicit authorization is needed since BaseController already restricts to admins
|
||||
# authorize @portfolio_transaction
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Portfolio Transaction ##{@portfolio_transaction.id}",
|
||||
path: admin_portfolio_transaction_path(@portfolio_transaction) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
@portfolio_transaction = PortfolioTransaction.new(portfolio_transaction_params)
|
||||
# TODO: Determine if explicit authorization is needed since BaseController already restricts to admins
|
||||
# authorize @portfolio_transaction
|
||||
|
||||
if @portfolio_transaction.save
|
||||
redirect_to admin_portfolio_transaction_path(@portfolio_transaction),
|
||||
notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Portfolio Transactions", path: "#" },
|
||||
{ label: "New" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
# TODO: Determine if explicit authorization is needed since BaseController already restricts to admins
|
||||
# authorize @portfolio_transaction
|
||||
|
||||
if @portfolio_transaction.update(portfolio_transaction_params)
|
||||
redirect_to admin_portfolio_transaction_path(@portfolio_transaction),
|
||||
notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Portfolio Transaction ##{@portfolio_transaction.id}",
|
||||
path: admin_portfolio_transaction_path(@portfolio_transaction) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
# TODO: Determine if explicit authorization is needed since BaseController already restricts to admins
|
||||
# authorize @portfolio_transaction
|
||||
@portfolio_transaction.destroy
|
||||
|
||||
redirect_to admin_root_path, notice: t(".notice")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_portfolio_transaction
|
||||
@portfolio_transaction = PortfolioTransaction.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def portfolio_transaction_params
|
||||
params.expect(portfolio_transaction: %i[portfolio_id transaction_type reason description amount_cents])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,111 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SchoolYearsController < BaseController
|
||||
before_action :set_school_year, only: %i[show edit update destroy]
|
||||
|
||||
def index
|
||||
@school_years = apply_sorting(SchoolYear.includes(:school, :year), default: "id")
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "School Years" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@breadcrumbs = [
|
||||
{ label: "School Years", path: admin_school_years_path },
|
||||
{ label: @school_year.to_s }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@school_year = SchoolYear.new
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "School Years", path: admin_school_years_path },
|
||||
{ label: "New School Year" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
@breadcrumbs = [
|
||||
{ label: "School Years", path: admin_school_years_path },
|
||||
{ label: @school_year.to_s, path: admin_school_year_path(@school_year) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
school = School.find_by(id: school_year_params[:school_id])
|
||||
year = Year.find_by(id: school_year_params[:year_id])
|
||||
|
||||
unless school && year
|
||||
@school_year = SchoolYear.new(school_year_params)
|
||||
@school_year.valid?
|
||||
return render_new_with_errors
|
||||
end
|
||||
|
||||
@school_year = SchoolYear.create!(school:, year:)
|
||||
|
||||
if @school_year.persisted?
|
||||
redirect_to admin_school_year_path(@school_year), notice: t(".notice")
|
||||
else
|
||||
render_new_with_errors
|
||||
end
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
save_error(school_year_params)
|
||||
render_new_with_errors
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
@school_year = e.record
|
||||
render_new_with_errors
|
||||
end
|
||||
|
||||
def update
|
||||
if @school_year.update(school_year_params)
|
||||
redirect_to admin_school_year_path(@school_year), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "School Years", path: admin_school_years_path },
|
||||
{ label: @school_year.to_s, path: admin_school_year_path(@school_year) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @school_year.destroy
|
||||
redirect_to admin_school_years_path, notice: t(".notice")
|
||||
else
|
||||
redirect_to admin_school_years_path, alert: t(".delete_restricted")
|
||||
end
|
||||
rescue ActiveRecord::DeleteRestrictionError
|
||||
redirect_to admin_school_years_path, alert: t(".delete_restricted")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_school_year
|
||||
@school_year = SchoolYear.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def school_year_params
|
||||
params.expect(school_year: %i[school_id year_id])
|
||||
end
|
||||
|
||||
def render_new_with_errors
|
||||
@breadcrumbs = [
|
||||
{ label: "School Years", path: admin_school_years_path },
|
||||
{ label: "New School Year" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
|
||||
def save_error(params)
|
||||
@school_year = SchoolYear.new(params)
|
||||
@school_year.valid?
|
||||
@school_year.errors.add(:base, "A School year with this school and year already exists.")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,97 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SchoolsController < BaseController
|
||||
before_action :set_school, only: %i[show edit update destroy]
|
||||
|
||||
def index
|
||||
sort_column = params[:sort].presence || "name"
|
||||
sort_direction = params[:direction] == "desc" ? :desc : :asc
|
||||
|
||||
@schools = School.reorder(sort_column => sort_direction)
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Schools" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@breadcrumbs = [
|
||||
{ label: "Schools", path: admin_schools_path },
|
||||
{ label: @school.name }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@school = School.new
|
||||
set_form_data
|
||||
@breadcrumbs = [
|
||||
{ label: "Schools", path: admin_schools_path },
|
||||
{ label: "New School" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
set_form_data
|
||||
@breadcrumbs = [
|
||||
{ label: "Schools", path: admin_schools_path },
|
||||
{ label: @school.name, path: admin_school_path(@school) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
year_ids = school_params[:year_ids]&.reject(&:blank?)
|
||||
@school = School.new(school_params.except(:year_ids))
|
||||
@school.year_ids = year_ids if year_ids.present?
|
||||
|
||||
if @school.save
|
||||
redirect_to admin_school_path(@school), notice: t(".notice")
|
||||
else
|
||||
set_form_data
|
||||
@breadcrumbs = [
|
||||
{ label: "Schools", path: admin_schools_path },
|
||||
{ label: "New School" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
year_ids = school_params[:year_ids]&.reject(&:blank?)
|
||||
update_params = school_params.except(:year_ids)
|
||||
update_params[:year_ids] = year_ids || []
|
||||
|
||||
if @school.update(update_params)
|
||||
redirect_to admin_school_path(@school), notice: t(".notice")
|
||||
else
|
||||
set_form_data
|
||||
@breadcrumbs = [
|
||||
{ label: "Schools", path: admin_schools_path },
|
||||
{ label: @school.name, path: admin_school_path(@school) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@school.destroy
|
||||
redirect_to admin_schools_path, notice: t(".notice")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_school
|
||||
@school = School.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def set_form_data
|
||||
@years = Year.ordered_by_start_year
|
||||
end
|
||||
|
||||
def school_params
|
||||
params.expect(school: [:name, { year_ids: [] }])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class StocksController < BaseController
|
||||
before_action :set_stock, only: %i[show edit update destroy]
|
||||
|
||||
def index
|
||||
@stocks = apply_sorting(Stock.all, default: "ticker")
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Stocks" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@breadcrumbs = [
|
||||
{ label: "Stocks", path: admin_stocks_path },
|
||||
{ label: @stock.ticker }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@stock = Stock.new
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Stocks", path: admin_stocks_path },
|
||||
{ label: "New Stock" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
@breadcrumbs = [
|
||||
{ label: "Stocks", path: admin_stocks_path },
|
||||
{ label: @stock.ticker, path: admin_stock_path(@stock) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
@stock = Stock.new(stock_params)
|
||||
|
||||
if @stock.save
|
||||
redirect_to admin_stock_path(@stock), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Stocks", path: admin_stocks_path },
|
||||
{ label: "New Stock" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @stock.update(stock_params)
|
||||
redirect_to admin_stock_path(@stock), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Stocks", path: admin_stocks_path },
|
||||
{ label: @stock.ticker, path: admin_stock_path(@stock) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@stock.destroy
|
||||
redirect_to admin_stocks_path, notice: t(".notice")
|
||||
rescue ActiveRecord::DeleteRestrictionError => e
|
||||
redirect_to admin_stocks_path, alert: "Cannot delete stock: #{e.message}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_stock
|
||||
@stock = Stock.find(params.expect(:id))
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def stock_params
|
||||
params.expect(
|
||||
stock: %i[
|
||||
ticker
|
||||
company_name
|
||||
company_website
|
||||
stock_exchange
|
||||
description
|
||||
price_cents
|
||||
yesterday_price_cents
|
||||
employees
|
||||
industry
|
||||
cash_flow
|
||||
debt
|
||||
debt_to_equity
|
||||
profit_margin
|
||||
sales_growth
|
||||
industry_avg_debt_to_equity
|
||||
industry_avg_profit_margin
|
||||
industry_avg_sales_growth
|
||||
management
|
||||
competitor_names
|
||||
archived
|
||||
]
|
||||
)
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,235 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
class StudentsController < BaseController
|
||||
include SoftDeletableFiltering
|
||||
|
||||
before_action :set_student, only: %i[show edit update destroy add_transaction]
|
||||
before_action :set_discarded_student, only: %i[restore]
|
||||
|
||||
def index
|
||||
@students = apply_sorting(scoped_by_discard_status(Student), default: "username")
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Students" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@attendance_entries = AttendanceEntryPresenter.for_student(@student)
|
||||
@earnings_summary = EarningsSummary.new(@student.portfolio) if @student.portfolio.present?
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Students", path: admin_students_path },
|
||||
{ label: @student.username }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@student = Student.new
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Students", path: admin_students_path },
|
||||
{ label: "New Student" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
@breadcrumbs = [
|
||||
{ label: "Students", path: admin_students_path },
|
||||
{ label: @student.username, path: admin_student_path(@student) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
@student = Student.new(student_params)
|
||||
|
||||
# Generate a memorable password if not provided
|
||||
if @student.password.blank?
|
||||
generated_password = MemorablePasswordGenerator.generate
|
||||
@student.password = generated_password
|
||||
@student.password_confirmation = generated_password
|
||||
else
|
||||
generated_password = @student.password
|
||||
end
|
||||
|
||||
if @student.save
|
||||
redirect_to admin_student_path(@student),
|
||||
notice: t(".notice", username: @student.username, password: generated_password)
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Students", path: admin_students_path },
|
||||
{ label: "New Student" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @student.update(student_params)
|
||||
redirect_to admin_student_path(@student), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Students", path: admin_students_path },
|
||||
{ label: @student.username, path: admin_student_path(@student) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
username = @student.username
|
||||
@student.discard
|
||||
redirect_to admin_students_path, notice: t(".notice", username: username)
|
||||
end
|
||||
|
||||
def restore
|
||||
username = @student.username
|
||||
@student.undiscard
|
||||
redirect_to admin_students_path(discarded: true), notice: t(".notice", username: username)
|
||||
end
|
||||
|
||||
def add_transaction
|
||||
errors = validate_transaction_params
|
||||
|
||||
if errors.present?
|
||||
redirect_to edit_admin_student_path(@student), alert: errors.join(", ")
|
||||
else
|
||||
transaction = PortfolioTransaction.new(
|
||||
portfolio: @student.portfolio,
|
||||
amount_cents: transaction_amount_cents,
|
||||
transaction_type: transaction_type,
|
||||
reason: transaction_reason,
|
||||
description: transaction_description
|
||||
)
|
||||
|
||||
if transaction.save
|
||||
redirect_to admin_student_path(@student), notice: t(".notice")
|
||||
else
|
||||
redirect_to edit_admin_student_path(@student), alert: transaction.errors.full_messages.join(", ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def import
|
||||
return redirect_with_missing_file_error if params[:csv_file].blank?
|
||||
|
||||
begin
|
||||
results = BulkStudentImportService.import_from_csv(params.expect(:csv_file).path)
|
||||
redirect_with_import_results(results)
|
||||
rescue CSV::MalformedCSVError => e
|
||||
redirect_to admin_students_path, alert: "Invalid CSV format: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def template
|
||||
send_data BulkStudentImportService.generate_csv_template,
|
||||
filename: "student_import_template.csv",
|
||||
type: "text/csv",
|
||||
disposition: "attachment"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_discarded_student
|
||||
@student = Student.with_discarded.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def set_student
|
||||
@student = Student.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def student_params
|
||||
params.expect(student: %i[username classroom_id password password_confirmation])
|
||||
end
|
||||
|
||||
def transaction_params
|
||||
params.permit(:add_fund_amount, :transaction_type, :transaction_reason, :transaction_description)
|
||||
end
|
||||
|
||||
def transaction_amount_cents
|
||||
amount = transaction_params[:add_fund_amount]
|
||||
amount.present? ? (amount.to_f * 100).to_i : nil
|
||||
end
|
||||
|
||||
def transaction_type
|
||||
transaction_params[:transaction_type]
|
||||
end
|
||||
|
||||
def transaction_reason
|
||||
transaction_params[:transaction_reason]
|
||||
end
|
||||
|
||||
def transaction_description
|
||||
transaction_params[:transaction_description]
|
||||
end
|
||||
|
||||
def validate_transaction_params
|
||||
errors = []
|
||||
errors << t("admin.students.add_transaction.errors.transaction_type_blank") if transaction_type.blank?
|
||||
errors << t("admin.students.add_transaction.errors.amount_blank") if transaction_amount_cents.blank?
|
||||
errors << t("admin.students.add_transaction.errors.reason_blank") if transaction_reason.blank?
|
||||
errors
|
||||
end
|
||||
|
||||
def redirect_with_missing_file_error
|
||||
redirect_to admin_students_path, alert: t(".errors.no_file")
|
||||
end
|
||||
|
||||
def redirect_with_import_results(results)
|
||||
return redirect_with_no_results_error if results.empty?
|
||||
|
||||
created, skipped, failed = partition_results(results)
|
||||
success_messages = build_success_messages(created, skipped)
|
||||
|
||||
if failed.any?
|
||||
redirect_with_mixed_results(success_messages, failed)
|
||||
else
|
||||
redirect_to admin_students_path, notice: success_messages.join(". ")
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_with_no_results_error
|
||||
redirect_to admin_students_path, alert: t(".errors.no_students")
|
||||
end
|
||||
|
||||
def partition_results(results)
|
||||
[
|
||||
results.select(&:created?),
|
||||
results.select(&:skipped?),
|
||||
results.select(&:failed?)
|
||||
]
|
||||
end
|
||||
|
||||
def build_success_messages(created, skipped)
|
||||
messages = []
|
||||
messages << build_created_message(created) if created.any?
|
||||
messages << build_skipped_message(skipped) if skipped.any?
|
||||
messages
|
||||
end
|
||||
|
||||
def build_created_message(created)
|
||||
usernames = created.map { |item| item.student.username }
|
||||
"Successfully created #{created.count} students: #{usernames.join(', ')}"
|
||||
end
|
||||
|
||||
def build_skipped_message(skipped)
|
||||
"Skipped #{skipped.count} existing usernames"
|
||||
end
|
||||
|
||||
def redirect_with_mixed_results(success_messages, failed)
|
||||
error_messages = failed.map { |item| "Row #{item.line_number}: #{item.error_message}" }
|
||||
alert_message = "#{failed.count} errors occurred: #{error_messages.join(', ')}"
|
||||
|
||||
if success_messages.any?
|
||||
redirect_to admin_students_path, notice: success_messages.join(". "), alert: alert_message
|
||||
else
|
||||
redirect_to admin_students_path, alert: alert_message
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ClassLength
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
module Teachers
|
||||
class DeactivationsController < BaseController
|
||||
before_action :set_teacher
|
||||
|
||||
def create
|
||||
username = @teacher.username
|
||||
@teacher.discard
|
||||
redirect_back_or_to admin_teachers_path, notice: t(".notice", username: username)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_teacher
|
||||
@teacher = Teacher.find(params.expect(:teacher_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
module Teachers
|
||||
class ReactivationsController < BaseController
|
||||
before_action :set_teacher
|
||||
|
||||
def create
|
||||
username = @teacher.username
|
||||
@teacher.undiscard
|
||||
redirect_back_or_to admin_teachers_path, notice: t(".notice", username: username)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_teacher
|
||||
@teacher = Teacher.with_discarded.find(params.expect(:teacher_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,115 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class TeachersController < BaseController
|
||||
include SoftDeletableFiltering
|
||||
|
||||
before_action :set_teacher, only: %i[show edit update destroy]
|
||||
before_action :require_deactivated, only: %i[destroy]
|
||||
|
||||
def index
|
||||
@teachers = apply_sorting(scoped_by_discard_status(Teacher), default: "username")
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Teachers" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@breadcrumbs = [
|
||||
{ label: "Teachers", path: admin_teachers_path },
|
||||
{ label: @teacher.username }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@teacher = Teacher.new
|
||||
set_form_data
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Teachers", path: admin_teachers_path },
|
||||
{ label: "New Teacher" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
set_form_data
|
||||
@breadcrumbs = [
|
||||
{ label: "Teachers", path: admin_teachers_path },
|
||||
{ label: @teacher.username, path: admin_teacher_path(@teacher) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
temp_password = Devise.friendly_token.first(20)
|
||||
classroom_ids = teacher_params[:classroom_ids]&.reject(&:blank?)
|
||||
|
||||
@teacher = Teacher.new(teacher_params.except(:classroom_ids).merge(password: temp_password))
|
||||
@teacher.classroom_ids = classroom_ids if classroom_ids.present?
|
||||
|
||||
if @teacher.save
|
||||
@teacher.send_reset_password_instructions
|
||||
redirect_to admin_teacher_path(@teacher), notice: t(".notice")
|
||||
else
|
||||
set_form_data
|
||||
@breadcrumbs = [
|
||||
{ label: "Teachers", path: admin_teachers_path },
|
||||
{ label: "New Teacher" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
classroom_ids = teacher_params[:classroom_ids]&.reject(&:blank?)
|
||||
update_params = teacher_params.except(:classroom_ids)
|
||||
update_params[:classroom_ids] = classroom_ids if classroom_ids.present?
|
||||
|
||||
if @teacher.update(update_params)
|
||||
redirect_to admin_teacher_path(@teacher), notice: t(".notice")
|
||||
else
|
||||
set_form_data
|
||||
@breadcrumbs = [
|
||||
{ label: "Teachers", path: admin_teachers_path },
|
||||
{ label: @teacher.username, path: admin_teacher_path(@teacher) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
username = @teacher.username
|
||||
@teacher.really_destroy!
|
||||
redirect_to admin_teachers_path, notice: t(".notice", username: username)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_teacher
|
||||
@teacher = Teacher.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def require_deactivated
|
||||
return if @teacher.discarded?
|
||||
|
||||
redirect_to edit_admin_teacher_path(@teacher), alert: t("admin.teachers.destroy.must_be_deactivated")
|
||||
end
|
||||
|
||||
def teacher_params
|
||||
params.expect(teacher: [:email, :name, { classroom_ids: [] }])
|
||||
end
|
||||
|
||||
def set_form_data
|
||||
active_years = Year.current_school_year(Date.current)
|
||||
@schools = School.joins(:school_years).where(school_years: { year_id: active_years.ids }).distinct.order(:name)
|
||||
|
||||
@selected_school_id = params[:school_id] || @teacher.classrooms.first&.school&.id
|
||||
|
||||
@classrooms = Classroom.active.joins(:school_year).where(school_years: { year_id: active_years.ids })
|
||||
@classrooms = @classrooms.where(school_years: { school_id: @selected_school_id }) if @selected_school_id.present?
|
||||
@classrooms = @classrooms.order(:name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class UsersController < BaseController
|
||||
before_action :set_user, only: %i[show edit update destroy]
|
||||
|
||||
def index
|
||||
@users = apply_sorting(User.all, default: "username")
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Users" }
|
||||
]
|
||||
end
|
||||
|
||||
def show
|
||||
@breadcrumbs = [
|
||||
{ label: "Users", path: admin_users_path },
|
||||
{ label: @user.username }
|
||||
]
|
||||
end
|
||||
|
||||
def new
|
||||
@user = User.new
|
||||
|
||||
@breadcrumbs = [
|
||||
{ label: "Users", path: admin_users_path },
|
||||
{ label: "New User" }
|
||||
]
|
||||
end
|
||||
|
||||
def edit
|
||||
@breadcrumbs = [
|
||||
{ label: "Users", path: admin_users_path },
|
||||
{ label: @user.username, path: admin_user_path(@user) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.new(user_params)
|
||||
|
||||
if @user.save
|
||||
redirect_to admin_user_path(@user), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Users", path: admin_users_path },
|
||||
{ label: "New User" }
|
||||
]
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @user.update(user_params)
|
||||
redirect_to admin_user_path(@user), notice: t(".notice")
|
||||
else
|
||||
@breadcrumbs = [
|
||||
{ label: "Users", path: admin_users_path },
|
||||
{ label: @user.username, path: admin_user_path(@user) },
|
||||
{ label: "Edit" }
|
||||
]
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@user.destroy
|
||||
redirect_to admin_users_path, notice: t(".notice")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = User.find(params.expect(:id))
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.expect(user: %i[username email type admin classroom_id password password_confirmation])
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user