diff --git a/Gemfile b/Gemfile index a62907b..7bc3434 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,7 @@ gem "kamal", require: false gem "thruster", require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] -# gem "image_processing", "~> 1.2" +gem 'image_processing', '~> 1.12' group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem diff --git a/Gemfile.lock b/Gemfile.lock index 252bb12..c9bf4d8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -108,6 +108,7 @@ GEM erubi (1.13.1) et-orbi (1.4.0) tzinfo + ffi (1.17.2-x86_64-linux-gnu) fugit (1.11.2) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) @@ -115,6 +116,9 @@ GEM activesupport (>= 6.1) i18n (1.14.7) concurrent-ruby (~> 1.0) + image_processing (1.14.0) + mini_magick (>= 4.9.5, < 6) + ruby-vips (>= 2.0.17, < 3) importmap-rails (2.2.2) actionpack (>= 6.0.0) activesupport (>= 6.0.0) @@ -152,6 +156,8 @@ GEM net-smtp marcel (1.1.0) matrix (0.4.3) + mini_magick (5.3.1) + logger mini_mime (1.1.5) minitest (5.25.5) msgpack (1.8.0) @@ -269,6 +275,9 @@ GEM rubocop-performance (>= 1.24) rubocop-rails (>= 2.30) ruby-progressbar (1.13.0) + ruby-vips (2.2.5) + ffi (~> 1.12) + logger rubyzip (3.1.1) securerandom (0.4.1) selenium-webdriver (4.35.0) @@ -340,6 +349,7 @@ DEPENDENCIES brakeman capybara debug + image_processing (~> 1.12) importmap-rails jbuilder kamal diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0d95db2..8176851 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,8 @@ class ApplicationController < ActionController::Base # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. allow_browser versions: :modern + before_action :set_assets_url + def set_assets_url + Rails.application.routes.default_url_options[:host] = "10.159.208.233:3000" + end end diff --git a/app/controllers/exclusive_traditional_records_controller.rb b/app/controllers/exclusive_traditional_records_controller.rb new file mode 100644 index 0000000..d0d0aad --- /dev/null +++ b/app/controllers/exclusive_traditional_records_controller.rb @@ -0,0 +1,70 @@ +class ExclusiveTraditionalRecordsController < ApplicationController + before_action :set_exclusive_traditional_record, only: %i[ show edit update destroy ] + + # GET /exclusive_traditional_records or /exclusive_traditional_records.json + def index + @exclusive_traditional_records = ExclusiveTraditionalRecord.all + end + + # GET /exclusive_traditional_records/1 or /exclusive_traditional_records/1.json + def show + end + + # GET /exclusive_traditional_records/new + def new + @exclusive_traditional_record = ExclusiveTraditionalRecord.new + end + + # GET /exclusive_traditional_records/1/edit + def edit + end + + # POST /exclusive_traditional_records or /exclusive_traditional_records.json + def create + @exclusive_traditional_record = ExclusiveTraditionalRecord.new(exclusive_traditional_record_params) + + respond_to do |format| + if @exclusive_traditional_record.save + format.html { redirect_to @exclusive_traditional_record, notice: "Exclusive traditional record was successfully created." } + format.json { render :show, status: :created, location: @exclusive_traditional_record } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @exclusive_traditional_record.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /exclusive_traditional_records/1 or /exclusive_traditional_records/1.json + def update + respond_to do |format| + if @exclusive_traditional_record.update(exclusive_traditional_record_params) + format.html { redirect_to @exclusive_traditional_record, notice: "Exclusive traditional record was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @exclusive_traditional_record } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @exclusive_traditional_record.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /exclusive_traditional_records/1 or /exclusive_traditional_records/1.json + def destroy + @exclusive_traditional_record.destroy! + + respond_to do |format| + format.html { redirect_to exclusive_traditional_records_path, notice: "Exclusive traditional record was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_exclusive_traditional_record + @exclusive_traditional_record = ExclusiveTraditionalRecord.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def exclusive_traditional_record_params + params.require(:exclusive_traditional_record).permit(:name, :author, :description, :pdf_file) + end +end diff --git a/app/controllers/expenses_controller.rb b/app/controllers/expenses_controller.rb new file mode 100644 index 0000000..3ecbee2 --- /dev/null +++ b/app/controllers/expenses_controller.rb @@ -0,0 +1,131 @@ +class ExpensesController < ApplicationController + before_action :set_expense, only: %i[ show edit update destroy ] + + # GET /expenses or /expenses.json + def index + @expenses = Expense.all + @expenses = Expense.order(created_at: :desc) + @total_expenses = Expense.sum(:amount) + @daily_expenses = Expense.where(created_at: Time.current.all_day).sum(:amount) + @weekly_expenses = Expense.where(created_at: Time.current.all_week).sum(:amount) + @monthly_expenses = Expense.where(created_at: Time.current.all_month).sum(:amount) + @daily_expense_items = Expense.where(created_at: Time.current.all_day) + @weekly_expense_items = Expense.where(created_at: Time.current.all_week) + @monthly_expense_items = Expense.where(created_at: Time.current.all_month) + end + + # GET /expenses/1 or /expenses/1.json + def show + @expense = Expense.find(params[:id]) + + # Totals + @total_expenses = Expense.sum(:amount) + @daily_expenses = Expense.where(created_at: Time.current.all_day).sum(:amount) + @weekly_expenses = Expense.where(created_at: Time.current.all_week).sum(:amount) + @monthly_expenses = Expense.where(created_at: Time.current.all_month).sum(:amount) + + # Lists + @daily_expense_items = Expense.where(created_at: Time.current.all_day) + @weekly_expense_items = Expense.where(created_at: Time.current.all_week) + @monthly_expense_items = Expense.where(created_at: Time.current.all_month) + end + + # GET /expenses/report + def report + set_summary + render :report + @daily_expense_items = Expense.where(date: Date.today.all_day) + @weekly_expense_items = Expense.where(date: Date.today.beginning_of_week..Date.today.end_of_week) + @monthly_expense_items = Expense.where(date: Date.today.beginning_of_month..Date.today.end_of_month) + + @daily_expenses = @daily_expense_items.sum(:amount) + @weekly_expenses = @weekly_expense_items.sum(:amount) + @monthly_expenses = @monthly_expense_items.sum(:amount) + + # For chart + @chart_labels = @monthly_expense_items.pluck(:date).map { |d| d.strftime("%d %b") } + @chart_data = @monthly_expense_items.pluck(:amount) + end + + # GET /expenses/new + def new + @expense = Expense.new + end + + # GET /expenses/1/edit + def edit + end + + # POST /expenses or /expenses.json + def create + @expense = Expense.new(expense_params) + + respond_to do |format| + if @expense.save + format.html { redirect_to @expense, notice: "Expense was successfully created." } + format.json { render :show, status: :created, location: @expense } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @expense.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /expenses/1 or /expenses/1.json + def update + respond_to do |format| + if @expense.update(expense_params) + format.html { redirect_to @expense, notice: "Expense was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @expense } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @expense.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /expenses/1 or /expenses/1.json + def destroy + @expense.destroy! + + respond_to do |format| + format.html { redirect_to expenses_path, notice: "Expense was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_expense + @expense = Expense.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def expense_params + params.require(:expense).permit(:amount, :date, :description) + end + def set_summary + @total_expenses = Expense.sum(:amount) + @daily_expenses = Expense.where(created_at: Time.current.all_day).sum(:amount) + @weekly_expenses = Expense.where(created_at: Time.current.all_week).sum(:amount) + @monthly_expenses = Expense.where(created_at: Time.current.all_month).sum(:amount) + + @daily_expense_items = Expense.where(created_at: Time.current.all_day) + @weekly_expense_items = Expense.where(created_at: Time.current.all_week) + @monthly_expense_items = Expense.where(created_at: Time.current.all_month) + + # Build chart data for the last 30 days (labels + totals per day) + days_back = 29 + end_date = Date.current + start_date = end_date - days_back + labels = [] + data = [] + (start_date..end_date).each do |d| + labels << d.strftime("%d %b") + day_total = Expense.where(created_at: d.beginning_of_day..d.end_of_day).sum(:amount) + data << day_total + end + @chart_labels = labels + @chart_data = data + end +end diff --git a/app/controllers/incomes_controller.rb b/app/controllers/incomes_controller.rb new file mode 100644 index 0000000..b635c18 --- /dev/null +++ b/app/controllers/incomes_controller.rb @@ -0,0 +1,91 @@ +class IncomesController < ApplicationController + before_action :set_income, only: %i[ show edit update destroy ] + + # GET /incomes + def index + @incomes = Income.all + end + + # GET /incomes/report + def report + # @incomes = Income.all + + # # Group incomes + # @daily_income = @incomes.where(date: Date.today).sum(:amount) + # @weekly_income = @incomes.where(date: Date.today.beginning_of_week..Date.today.end_of_week).sum(:amount) + # @monthly_income = @incomes.where(date: Date.today.beginning_of_month..Date.today.end_of_month).sum(:amount) + + # # List items + # @daily_income_items = @incomes.where(date: Date.today) + # @weekly_income_items = @incomes.where(date: Date.today.beginning_of_week..Date.today.end_of_week) + # @monthly_income_items = @incomes.where(date: Date.today.beginning_of_month..Date.today.end_of_month) + + # # Chart (Group by Month) + # grouped = @incomes.group_by { |i| i.date.strftime("%b") } + # @chart_labels = grouped.keys + # @chart_data = grouped.values.map { |items| items.sum(&:amount) } + @incomes = Income.all + + # Daily / Weekly / Monthly totals using scopes + @daily_income = Income.today.total_amount + @weekly_income = Income.this_week.total_amount + @monthly_income = Income.this_month.total_amount + + # For charts + @chart_labels = (0..6).map { |i| (Date.today - i).strftime("%d %b") }.reverse + @chart_data = @chart_labels.map { |d| Income.where(created_at: d.to_date.all_day).sum(:amount) } + end + + def show; end + + def new + @income = Income.new + end + + def edit; end + + def create + @income = Income.new(income_params) + + respond_to do |format| + if @income.save + format.html { redirect_to @income, notice: "Income was successfully created." } + format.json { render :show, status: :created, location: @income } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @income.errors, status: :unprocessable_entity } + end + end + end + + def update + respond_to do |format| + if @income.update(income_params) + format.html { redirect_to @income, notice: "Income was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @income } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @income.errors, status: :unprocessable_entity } + end + end + end + + def destroy + @income.destroy! + + respond_to do |format| + format.html { redirect_to incomes_path, notice: "Income was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + + def set_income + @income = Income.find(params[:id]) + end + + def income_params + params.require(:income).permit(:title, :amount, :date, :category, :description) + end +end diff --git a/app/controllers/institutions_controller.rb b/app/controllers/institutions_controller.rb index 636870d..0e0cfbc 100644 --- a/app/controllers/institutions_controller.rb +++ b/app/controllers/institutions_controller.rb @@ -2,17 +2,58 @@ class InstitutionsController < ApplicationController before_action :set_institution, only: %i[ show edit update destroy ] + # GET /institutions or /institutions.json def index + @incomes = Income.all @institutions = Institution.all @programs = Program.all @ziyaras = Ziyara.all + @students = Student.all + @institution_count = Institution.count + @program_count = Program.count + @student_count = Student.count + @ziyara_count = Ziyara.count + + @institutions = Institution.all + @programs = Program.all + @ziyaras = Ziyara.all + @exclusive_traditional_records = ExclusiveTraditionalRecord.all + @exclusive_traditional_records_count = ExclusiveTraditionalRecord.count + @expenses = Expense.all + @expenses = Expense.order(created_at: :desc) + + # Totals + @total_expenses = Expense.sum(:amount) + @daily_expenses = Expense.where(created_at: Time.current.all_day).sum(:amount) + @weekly_expenses = Expense.where(created_at: Time.current.all_week).sum(:amount) + @monthly_expenses = Expense.where(created_at: Time.current.all_month).sum(:amount) + + # Expense lists (MUST be here) + @daily_expense_items = Expense.where(created_at: Time.current.all_day) + @weekly_expense_items = Expense.where(created_at: Time.current.all_week) + @monthly_expense_items = Expense.where(created_at: Time.current.all_month) + + + # index is a collection action β do not attempt to load a single institution here + @students = Student.all + end + # Use callbacks to share common setup or constraints between actions. + def set_institution + @institution = Institution.find_by(id: params[:institution_id]) + end + + # GET /institutions/1 or /institutions/1.json def show end + # GET /institutions/contact + def contact + end + # GET /institutions/new def new @institution = Institution.new @@ -63,11 +104,13 @@ class InstitutionsController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_institution - @institution = Institution.find(params.expect(:id)) + @institution = Institution.find(params[:id]) end # Only allow a list of trusted parameters through. def institution_params - params.expect(institution: [ :name, :institution_type, :place ]) + params.require(:institution).permit(:name, :institution_type, :place) end + + end diff --git a/app/controllers/programs_controller.rb b/app/controllers/programs_controller.rb index 56a1872..6905a3c 100644 --- a/app/controllers/programs_controller.rb +++ b/app/controllers/programs_controller.rb @@ -60,11 +60,11 @@ class ProgramsController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_program - @program = Program.find(params.expect(:id)) + @program = Program.find(params[:id]) end # Only allow a list of trusted parameters through. def program_params - params.expect(program: [ :name, :date, :leadingPerson ]) + params.require(:program).permit(:name, :date, :leadingPerson, :description) end end diff --git a/app/controllers/students_controller.rb b/app/controllers/students_controller.rb index cdb69f3..2929b0f 100644 --- a/app/controllers/students_controller.rb +++ b/app/controllers/students_controller.rb @@ -1,10 +1,19 @@ class StudentsController < ApplicationController + # set_institution will try to load an institution when institution_id is present before_action :set_institution + # set_student will safely find a student either scoped to @institution or globally before_action :set_student, only: %i[ show edit update destroy ] + # ensure nested behaviour for index/new/create (these expect an institution) + before_action :ensure_institution_for_nested_actions, only: %i[index new create] # GET /students or /students.json def index - @students = @institution.students + if @institution + @students = @institution.students + else + # show all students when not nested + @students = Student.all + end end # GET /students/1 or /students/1.json @@ -21,14 +30,28 @@ class StudentsController < ApplicationController end # POST /students or /students.json + # def create + # @student = @institution.students.build(student_params) + # if @student.save + # redirect_to institution_students_path(@institution), notice: 'Student was successfully created.' + # else + # render :new, status: :unprocessable_entity + # end + # end def create - @student = @institution.students.build(student_params) - if @student.save - redirect_to institution_students_path(@institution), notice: 'Student was successfully created.' - else - render :new, status: :unprocessable_entity + @student = @institution.students.new(student_params) + + if @student.save + respond_to do |format| + format.js # creates -> create.js.erb + end + else + respond_to do |format| + format.js { render :error } end end + end + # PATCH/PUT /students/1 or /students/1.json def update @@ -48,15 +71,33 @@ class StudentsController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_institution + return unless params[:institution_id].present? @institution = Institution.find(params[:institution_id]) + rescue ActiveRecord::RecordNotFound + # Let other parts handle missing institution (redirects in actions) + @institution = nil end def set_student - @student = @institution.students.find(params[:id]) + if @institution.present? + @student = @institution.students.find(params[:id]) + else + # fallback to global lookup when not nested + @student = Student.find(params[:id]) if params[:id].present? + @institution = @student.institution if @student.present? + end + rescue ActiveRecord::RecordNotFound + # will raise in actions if record missing + raise + end + + def ensure_institution_for_nested_actions + return if @institution.present? + redirect_to institutions_path, alert: 'Please select an Institution first.' end # Only allow a list of trusted parameters through. def student_params - params.require(:student).permit(:first_name, :last_name, :email, :institution_id) + params.require(:student).permit(:first_name, :last_name, :email, :institution_id, :place, :phone_number, :age, :photo) end end diff --git a/app/controllers/ziyaras_controller.rb b/app/controllers/ziyaras_controller.rb index 9eba6ee..6bdf32c 100644 --- a/app/controllers/ziyaras_controller.rb +++ b/app/controllers/ziyaras_controller.rb @@ -60,11 +60,11 @@ class ZiyarasController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_ziyara - @ziyara = Ziyara.find(params.expect(:id)) + @ziyara = Ziyara.find(params[:id]) end # Only allow a list of trusted parameters through. def ziyara_params - params.expect(ziyara: [ :name ]) + params.require(:ziyara).permit(:name, :date, :location, :description) end end diff --git a/app/helpers/exclusive_traditional_records_helper.rb b/app/helpers/exclusive_traditional_records_helper.rb new file mode 100644 index 0000000..0b51216 --- /dev/null +++ b/app/helpers/exclusive_traditional_records_helper.rb @@ -0,0 +1,2 @@ +module ExclusiveTraditionalRecordsHelper +end diff --git a/app/helpers/expenses_helper.rb b/app/helpers/expenses_helper.rb new file mode 100644 index 0000000..1d50062 --- /dev/null +++ b/app/helpers/expenses_helper.rb @@ -0,0 +1,2 @@ +module ExpensesHelper +end diff --git a/app/helpers/incomes_helper.rb b/app/helpers/incomes_helper.rb new file mode 100644 index 0000000..4f2262e --- /dev/null +++ b/app/helpers/incomes_helper.rb @@ -0,0 +1,2 @@ +module IncomesHelper +end diff --git a/app/models/exclusive_traditional_record.rb b/app/models/exclusive_traditional_record.rb new file mode 100644 index 0000000..a506565 --- /dev/null +++ b/app/models/exclusive_traditional_record.rb @@ -0,0 +1,3 @@ +class ExclusiveTraditionalRecord < ApplicationRecord + has_one_attached :pdf_file +end diff --git a/app/models/expense.rb b/app/models/expense.rb new file mode 100644 index 0000000..3015508 --- /dev/null +++ b/app/models/expense.rb @@ -0,0 +1,8 @@ +class Expense < ApplicationRecord + scope :today, -> { where(created_at: Time.current.all_day) } + scope :this_week, -> { where(created_at: Time.current.all_week) } + scope :this_month, -> { where(created_at: Time.current.all_month) } + + validates :amount, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :date, presence: true +end diff --git a/app/models/income.rb b/app/models/income.rb new file mode 100644 index 0000000..2ea4bb1 --- /dev/null +++ b/app/models/income.rb @@ -0,0 +1,13 @@ +class Income < ApplicationRecord + # Todayβs income + scope :today, -> { where("created_at >= ?", Time.zone.now.beginning_of_day) } + + # This week income + scope :this_week, -> { where(created_at: Time.zone.now.beginning_of_week..Time.zone.now.end_of_week) } + + # This month income + scope :this_month, -> { where(created_at: Time.zone.now.beginning_of_month..Time.zone.now.end_of_month) } + + # Optional: total amount + scope :total_amount, -> { sum(:amount) } +end diff --git a/app/models/student.rb b/app/models/student.rb index a0c4969..dd27047 100644 --- a/app/models/student.rb +++ b/app/models/student.rb @@ -1,3 +1,4 @@ class Student < ApplicationRecord belongs_to :institution + has_one_attached :photo end diff --git a/app/views/exclusive_traditional_records/_exclusive_traditional_record.html.erb b/app/views/exclusive_traditional_records/_exclusive_traditional_record.html.erb new file mode 100644 index 0000000..28bdfbe --- /dev/null +++ b/app/views/exclusive_traditional_records/_exclusive_traditional_record.html.erb @@ -0,0 +1,26 @@ +
+ Name: + <%= exclusive_traditional_record.name %> +
+ ++ Author: + <%= exclusive_traditional_record.author %> +
+ ++ Description: + <%= exclusive_traditional_record.description %> +
+ +<%= notice %>
+ +<% content_for :title, "Exclusive traditional records" %> + ++ <%= link_to "Show this exclusive traditional record", exclusive_traditional_record %> +
+ <% end %> +<%= notice %>
+ +<%= render @exclusive_traditional_record %> + ++ Amount: + <%= expense.amount %> +
+ ++ Date: + <%= expense.date %> +
+ +<%= notice %>
+ +<% content_for :title, "Expenses" %> + +| Amount | Date |
|---|---|
| <%= number_to_currency(expense.amount, unit: "βΉ") %> | +<%= expense.created_at.strftime("%d %b %Y") %> | +
| No expenses for today. | |
| Total | <%= number_to_currency(@daily_expenses || 0, unit: "βΉ") %> |
| Amount | Date |
|---|---|
| <%= number_to_currency(expense.amount, unit: "βΉ") %> | +<%= expense.created_at.strftime("%d %b %Y") %> | +
| No expenses for this week. | |
| Total | <%= number_to_currency(@weekly_expenses || 0, unit: "βΉ") %> |
| Amount | Date |
|---|---|
| <%= number_to_currency(expense.amount, unit: "βΉ") %> | +<%= expense.created_at.strftime("%d %b %Y") %> | +
| No expenses for this month. | |
| Total | <%= number_to_currency(@monthly_expenses || 0, unit: "βΉ") %> |
<%= notice %>
+ +<%= render @expense %> + ++ Title: + <%= income.title %> +
+ ++ Amount: + <%= income.amount %> +
+ ++ Date: + <%= income.date %> +
+ ++ Category: + <%= income.category %> +
+ ++ Description: + <%= income.description %> +
+ +<%= notice %>
+ +<% content_for :title, "Incomes" %> + +| Amount | Date |
|---|---|
| <%= number_to_currency(income.amount, unit: "βΉ") %> | +<%= income.created_at.strftime("%d %b %Y") %> | +
| No income for today. | |
| Total | <%= number_to_currency(@daily_income || 0, unit: "βΉ") %> |
| Amount | Date |
|---|---|
| <%= number_to_currency(income.amount, unit: "βΉ") %> | +<%= income.created_at.strftime("%d %b %Y") %> | +
| No income for this week. | |
| Total | <%= number_to_currency(@weekly_income || 0, unit: "βΉ") %> |
| Amount | Date |
|---|---|
| <%= number_to_currency(income.amount, unit: "βΉ") %> | +<%= income.created_at.strftime("%d %b %Y") %> | +
| No income for this month. | |
| Total | <%= number_to_currency(@monthly_income || 0, unit: "βΉ") %> |
<%= notice %>
+ +<%= render @income %> + +If you have any questions or need assistance, please feel free to reach out to us:
+<%= institution.description.truncate(100) %>
+<%= institution.description.truncate(100) %>
<% end %> - +No institutions added yet.
<% end %>
- <% if program.respond_to?(:date) %>
- Date: <%= program.date %>
- <% end %>
- <% if program.respond_to?(:leadingPerson) %>
- Lead: <%= program.leadingPerson %>
- <% end %>
-
Date: <%= program.date %>
+ <% end %> + <% if program.respond_to?(:leadingPerson) %> +Lead: <%= program.leadingPerson %>
+ <% end %> @@ -273,43 +295,226 @@π Location: <%= ziyara.location %>
- <% end %> - <% if ziyara.respond_to?(:date) %> -π Date: <%= ziyara.date %>
- <% end %> - +<% content_for :title, "Expenses" %> + + + + + + +No Ziyara records yet.
<% end %> -ID: <%= @institution.id %>
<%# Add more institution details here if needed %>Email: <%= student.email %>
-ID: <%= student.id %>