This commit is contained in:
niyas301 2025-10-23 16:11:16 +05:30
parent 4991d8d4b6
commit d13227e897
73 changed files with 2724 additions and 284 deletions

View File

@ -38,7 +38,7 @@ gem "kamal", require: false
gem "thruster", require: false gem "thruster", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # 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 group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem

View File

@ -108,6 +108,7 @@ GEM
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.4.0) et-orbi (1.4.0)
tzinfo tzinfo
ffi (1.17.2-x86_64-linux-gnu)
fugit (1.11.2) fugit (1.11.2)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4) raabro (~> 1.4)
@ -115,6 +116,9 @@ GEM
activesupport (>= 6.1) activesupport (>= 6.1)
i18n (1.14.7) i18n (1.14.7)
concurrent-ruby (~> 1.0) 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) importmap-rails (2.2.2)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0) activesupport (>= 6.0.0)
@ -152,6 +156,8 @@ GEM
net-smtp net-smtp
marcel (1.1.0) marcel (1.1.0)
matrix (0.4.3) matrix (0.4.3)
mini_magick (5.3.1)
logger
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.25.5) minitest (5.25.5)
msgpack (1.8.0) msgpack (1.8.0)
@ -269,6 +275,9 @@ GEM
rubocop-performance (>= 1.24) rubocop-performance (>= 1.24)
rubocop-rails (>= 2.30) rubocop-rails (>= 2.30)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-vips (2.2.5)
ffi (~> 1.12)
logger
rubyzip (3.1.1) rubyzip (3.1.1)
securerandom (0.4.1) securerandom (0.4.1)
selenium-webdriver (4.35.0) selenium-webdriver (4.35.0)
@ -340,6 +349,7 @@ DEPENDENCIES
brakeman brakeman
capybara capybara
debug debug
image_processing (~> 1.12)
importmap-rails importmap-rails
jbuilder jbuilder
kamal kamal

View File

@ -1,4 +1,8 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern 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 end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -2,17 +2,58 @@ class InstitutionsController < ApplicationController
before_action :set_institution, only: %i[ show edit update destroy ] before_action :set_institution, only: %i[ show edit update destroy ]
# GET /institutions or /institutions.json # GET /institutions or /institutions.json
def index def index
@incomes = Income.all
@institutions = Institution.all @institutions = Institution.all
@programs = Program.all @programs = Program.all
@ziyaras = Ziyara.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 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 # GET /institutions/1 or /institutions/1.json
def show def show
end end
# GET /institutions/contact
def contact
end
# GET /institutions/new # GET /institutions/new
def new def new
@institution = Institution.new @institution = Institution.new
@ -63,11 +104,13 @@ class InstitutionsController < ApplicationController
private private
# Use callbacks to share common setup or constraints between actions. # Use callbacks to share common setup or constraints between actions.
def set_institution def set_institution
@institution = Institution.find(params.expect(:id)) @institution = Institution.find(params[:id])
end end
# Only allow a list of trusted parameters through. # Only allow a list of trusted parameters through.
def institution_params def institution_params
params.expect(institution: [ :name, :institution_type, :place ]) params.require(:institution).permit(:name, :institution_type, :place)
end end
end end

View File

@ -60,11 +60,11 @@ class ProgramsController < ApplicationController
private private
# Use callbacks to share common setup or constraints between actions. # Use callbacks to share common setup or constraints between actions.
def set_program def set_program
@program = Program.find(params.expect(:id)) @program = Program.find(params[:id])
end end
# Only allow a list of trusted parameters through. # Only allow a list of trusted parameters through.
def program_params def program_params
params.expect(program: [ :name, :date, :leadingPerson ]) params.require(:program).permit(:name, :date, :leadingPerson, :description)
end end
end end

View File

@ -1,10 +1,19 @@
class StudentsController < ApplicationController class StudentsController < ApplicationController
# set_institution will try to load an institution when institution_id is present
before_action :set_institution 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 ] 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 # GET /students or /students.json
def index def index
if @institution
@students = @institution.students @students = @institution.students
else
# show all students when not nested
@students = Student.all
end
end end
# GET /students/1 or /students/1.json # GET /students/1 or /students/1.json
@ -21,14 +30,28 @@ class StudentsController < ApplicationController
end end
# POST /students or /students.json # 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 def create
@student = @institution.students.build(student_params) @student = @institution.students.new(student_params)
if @student.save if @student.save
redirect_to institution_students_path(@institution), notice: 'Student was successfully created.' respond_to do |format|
format.js # creates -> create.js.erb
end
else else
render :new, status: :unprocessable_entity respond_to do |format|
format.js { render :error }
end end
end end
end
# PATCH/PUT /students/1 or /students/1.json # PATCH/PUT /students/1 or /students/1.json
def update def update
@ -48,15 +71,33 @@ class StudentsController < ApplicationController
private private
# Use callbacks to share common setup or constraints between actions. # Use callbacks to share common setup or constraints between actions.
def set_institution def set_institution
return unless params[:institution_id].present?
@institution = Institution.find(params[:institution_id]) @institution = Institution.find(params[:institution_id])
rescue ActiveRecord::RecordNotFound
# Let other parts handle missing institution (redirects in actions)
@institution = nil
end end
def set_student def set_student
if @institution.present?
@student = @institution.students.find(params[:id]) @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 end
# Only allow a list of trusted parameters through. # Only allow a list of trusted parameters through.
def student_params 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
end end

View File

@ -60,11 +60,11 @@ class ZiyarasController < ApplicationController
private private
# Use callbacks to share common setup or constraints between actions. # Use callbacks to share common setup or constraints between actions.
def set_ziyara def set_ziyara
@ziyara = Ziyara.find(params.expect(:id)) @ziyara = Ziyara.find(params[:id])
end end
# Only allow a list of trusted parameters through. # Only allow a list of trusted parameters through.
def ziyara_params def ziyara_params
params.expect(ziyara: [ :name ]) params.require(:ziyara).permit(:name, :date, :location, :description)
end end
end end

View File

@ -0,0 +1,2 @@
module ExclusiveTraditionalRecordsHelper
end

View File

@ -0,0 +1,2 @@
module ExpensesHelper
end

View File

@ -0,0 +1,2 @@
module IncomesHelper
end

View File

@ -0,0 +1,3 @@
class ExclusiveTraditionalRecord < ApplicationRecord
has_one_attached :pdf_file
end

8
app/models/expense.rb Normal file
View File

@ -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

13
app/models/income.rb Normal file
View File

@ -0,0 +1,13 @@
class Income < ApplicationRecord
# Todays 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

View File

@ -1,3 +1,4 @@
class Student < ApplicationRecord class Student < ApplicationRecord
belongs_to :institution belongs_to :institution
has_one_attached :photo
end end

View File

@ -0,0 +1,26 @@
<div id="<%= dom_id exclusive_traditional_record %>">
<p>
<strong>Name:</strong>
<%= exclusive_traditional_record.name %>
</p>
<p>
<strong>Author:</strong>
<%= exclusive_traditional_record.author %>
</p>
<p>
<strong>Description:</strong>
<%= exclusive_traditional_record.description %>
</p>
<div>
<% if exclusive_traditional_record.pdf_file.attached? %>
<strong>PDF File:</strong>
<%= link_to exclusive_traditional_record.pdf_file.filename.to_s, rails_blob_path(exclusive_traditional_record.pdf_file, disposition: "attachment") %>
<% else %>
<em>No PDF file uploaded.</em>
<% end %>
</div>
</div>

View File

@ -0,0 +1,2 @@
json.extract! exclusive_traditional_record, :id, :name, :author, :description, :created_at, :updated_at
json.url exclusive_traditional_record_url(exclusive_traditional_record, format: :json)

View File

@ -0,0 +1,37 @@
<%= form_with(model: exclusive_traditional_record) do |form| %>
<% if exclusive_traditional_record.errors.any? %>
<div style="color: red">
<h2><%= pluralize(exclusive_traditional_record.errors.count, "error") %> prohibited this exclusive_traditional_record from being saved:</h2>
<ul>
<% exclusive_traditional_record.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :name, style: "display: block" %>
<%= form.text_field :name %>
</div>
<div>
<%= form.label :author, style: "display: block" %>
<%= form.text_field :author %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.textarea :description %>
</div>
<div>
<%= form.label :pdf_file, "Upload PDF", style: "display: block" %>
<%= form.file_field :pdf_file %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>

View File

@ -0,0 +1,12 @@
<% content_for :title, "Editing exclusive traditional record" %>
<h1>Editing exclusive traditional record</h1>
<%= render "form", exclusive_traditional_record: @exclusive_traditional_record %>
<br>
<div>
<%= link_to "Show this exclusive traditional record", @exclusive_traditional_record %> |
<%= link_to "Back to exclusive traditional records", exclusive_traditional_records_path %>
</div>

View File

@ -0,0 +1,16 @@
<p style="color: green"><%= notice %></p>
<% content_for :title, "Exclusive traditional records" %>
<h1>Exclusive traditional records</h1>
<div id="exclusive_traditional_records">
<% @exclusive_traditional_records.each do |exclusive_traditional_record| %>
<%= render exclusive_traditional_record %>
<p>
<%= link_to "Show this exclusive traditional record", exclusive_traditional_record %>
</p>
<% end %>
</div>
<%= link_to "New exclusive traditional record", new_exclusive_traditional_record_path %>

View File

@ -0,0 +1 @@
json.array! @exclusive_traditional_records, partial: "exclusive_traditional_records/exclusive_traditional_record", as: :exclusive_traditional_record

View File

@ -0,0 +1,11 @@
<% content_for :title, "New exclusive traditional record" %>
<h1>New exclusive traditional record</h1>
<%= render "form", exclusive_traditional_record: @exclusive_traditional_record %>
<br>
<div>
<%= link_to "Back to exclusive traditional records", exclusive_traditional_records_path %>
</div>

View File

@ -0,0 +1,10 @@
<p style="color: green"><%= notice %></p>
<%= render @exclusive_traditional_record %>
<div>
<%= link_to "Edit this exclusive traditional record", edit_exclusive_traditional_record_path(@exclusive_traditional_record) %> |
<%= link_to "Back to exclusive traditional records", exclusive_traditional_records_path %>
<%= button_to "Destroy this exclusive traditional record", @exclusive_traditional_record, method: :delete %>
</div>

View File

@ -0,0 +1 @@
json.partial! "exclusive_traditional_records/exclusive_traditional_record", exclusive_traditional_record: @exclusive_traditional_record

View File

@ -0,0 +1,12 @@
<div id="<%= dom_id expense %>">
<p>
<strong>Amount:</strong>
<%= expense.amount %>
</p>
<p>
<strong>Date:</strong>
<%= expense.date %>
</p>
</div>

View File

@ -0,0 +1,2 @@
json.extract! expense, :id, :amount, :date, :created_at, :updated_at
json.url expense_url(expense, format: :json)

View File

@ -0,0 +1,27 @@
<%= form_with(model: expense) do |form| %>
<% if expense.errors.any? %>
<div style="color: red">
<h2><%= pluralize(expense.errors.count, "error") %> prohibited this expense from being saved:</h2>
<ul>
<% expense.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :amount, style: "display: block" %>
<%= form.text_field :amount %>
</div>
<div>
<%= form.label :date, style: "display: block" %>
<%= form.date_field :date %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>

View File

@ -0,0 +1,12 @@
<% content_for :title, "Editing expense" %>
<h1>Editing expense</h1>
<%= render "form", expense: @expense %>
<br>
<div>
<%= link_to "Show this expense", @expense %> |
<%= link_to "Back to expenses", expenses_path %>
</div>

View File

@ -0,0 +1,104 @@
<p style="color: green"><%= notice %></p>
<% content_for :title, "Expenses" %>
<h1>Expenses</h1>
<div class="text-end mb-3">
<%= link_to "📊 Expense Report", expense_report_path, class: "btn btn-outline-primary" %>
</div>
<style>
/* Container for all cards */
#expenses {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
margin-top: 20px;
}
/* Individual expense card */
.expense-card-item {
background: linear-gradient(135deg, #f0f8ff, #e6f7ff); /* soft gradient */
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
width: 250px;
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* Hover effect for card */
.expense-card-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
/* Amount styling */
.expense-card-item .amount {
font-size: 1.4rem;
font-weight: 600;
color: #007bff;
margin-bottom: 10px;
}
/* Date styling */
.expense-card-item .date {
font-size: 0.9rem;
color: #555555;
margin-bottom: 15px;
}
/* Show link styling */
.expense-card-item .show-link {
align-self: flex-start;
background-color: #28a745;
color: white;
padding: 6px 12px;
border-radius: 6px;
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.expense-card-item .show-link:hover {
background-color: #1e7e34;
transform: scale(1.05);
}
.btn-expense {
display: inline-block;
background: linear-gradient(135deg, #6a11cb, #2575fc);
color: #fff;
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
text-decoration: none;
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.btn-expense:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
background: linear-gradient(135deg, #2575fc, #6a11cb);
}
</style>
<%= link_to "New Expense", new_expense_path, class: "btn-expense" %>
<div id="expenses">
<% @expenses.each do |expense| %>
<div class="expense-card-item">
<div class="amount"><%= number_to_currency(expense.amount) %></div>
<div class="date"><%= expense.created_at.strftime("%d %b %Y") %></div>
<%= link_to "Show this expense", expense, class: "show-link" %>
</div>
<% end %>
</div>

View File

@ -0,0 +1 @@
json.array! @expenses, partial: "expenses/expense", as: :expense

View File

@ -0,0 +1,11 @@
<% content_for :title, "New expense" %>
<h1>New expense</h1>
<%= render "form", expense: @expense %>
<br>
<div>
<%= link_to "Back to expenses", expenses_path %>
</div>

View File

@ -0,0 +1,284 @@
<% content_for :title, "Expense Report" %>
<!-- Minimal & Clean CSS (small and tidy) -->
<style>
:root{
--card-bg: #ffffff;
--muted: #6b7280;
--accent: #2563eb; /* blue */
--radius: 10px;
--shadow: 0 6px 20px rgba(16,24,40,0.06);
--gap: 18px;
font-family: "Inter", "Poppins", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}
.er-container {
max-width: 920px;
margin: 28px auto;
padding: 18px;
color: #111827;
}
.er-header {
display:flex;
align-items:center;
justify-content:space-between;
gap:12px;
margin-bottom: var(--gap);
}
.er-title {
color: #fff;
font-size:1.4rem;
font-weight:600;
margin:0;
}
.er-actions { display:flex; gap:10px; align-items:center; }
.btn-simple {
background: var(--accent);
color:#fff;
padding:8px 12px;
border-radius:8px;
text-decoration:none;
font-weight:600;
font-size:0.95rem;
box-shadow: 0 6px 16px rgba(37,99,235,0.12);
transition: transform .12s ease, box-shadow .12s ease;
}
.btn-simple:hover { transform: translateY(-3px); box-shadow: 0 10px 26px rgba(37,99,235,0.16); }
/* summary cards row */
.summary-row {
display: flex;
gap: 16px;
margin-bottom: 22px;
}
.card {
flex: 1;
padding: 16px;
border-radius: var(--radius);
background: #87ceeb; /* 🌤️ Sky Blue */
border: 1px solid var(--border);
backdrop-filter: blur(12px);
box-shadow: var(--shadowG3);
transition: .25s;
}
.card:hover {
transform: translateY(-6px) scale(1.02);
box-shadow: 0 12px 36px rgba(168,85,247,0.75);
}
.card small {
color: var(--muted);
font-weight: 500;
}
.card .value {
font-size: 1.4rem;
font-weight: 700;
margin-top: 6px;
}
/* chart wrapper */
.chart-wrap {
background: var(--card-bg);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding:12px;
margin-bottom: var(--gap);
border: 1px solid rgba(15,23,42,0.04);
}
/* tables */
.table {
width:100%;
border-collapse:collapse;
margin-bottom:20px;
background: transparent;
}
.table thead th {
text-align:left; font-size:0.9rem; color:var(--muted); padding:10px 12px;
border-bottom: 1px solid rgba(15,23,42,0.06);
}
.table tbody tr {
background: #fff; border-radius:8px;
}
.table td {
padding:11px 12px;
font-size:0.95rem;
vertical-align:middle;
border-bottom: 1px solid rgba(15,23,42,0.03);
}
.table tfoot th { padding:10px 12px; text-align:left; font-weight:700; border-top:1px solid rgba(15,23,42,0.06); }
/* responsive: stack cards on small screens */
@media (max-width:720px){
.summary-row { flex-direction:column; }
.er-header { flex-direction:column; align-items:flex-start; gap:10px; }
}
</style>
<div class="er-container">
<div class="er-header">
<h1 class="er-title">EXPENSE REPORT</h1>
<div class="er-actions">
<%= link_to "New Expense", new_expense_path, class: "btn-simple" %>
</div>
</div>
<!-- Summary -->
<div class="summary-row">
<div class="card">
<small>Today's Expenses</small>
<div class="value"><%= number_to_currency(@daily_expenses || 0, unit: "₹") %></div>
</div>
<div class="card">
<small>This Week</small>
<div class="value"><%= number_to_currency(@weekly_expenses || 0, unit: "₹") %></div>
</div>
<div class="card">
<small>This Month</small>
<div class="value"><%= number_to_currency(@monthly_expenses || 0, unit: "₹") %></div>
</div>
</div>
<!-- Chart -->
<div class="chart-wrap">
<canvas id="expensesChart" height="120"></canvas>
</div>
<!-- Today's table -->
<section>
<h3 style="margin:0 0 8px 0; font-size:1rem; color:#374151;">Today's Expenses</h3>
<table class="table" aria-describedby="today-expenses">
<thead>
<tr><th>Amount</th><th>Date</th></tr>
</thead>
<tbody>
<% (@daily_expense_items || []).each do |expense| %>
<tr>
<td><%= number_to_currency(expense.amount, unit: "₹") %></td>
<td><%= expense.created_at.strftime("%d %b %Y") %></td>
</tr>
<% end %>
<% if (@daily_expense_items || []).empty? %>
<tr><td colspan="2" style="padding:12px;color:var(--muted)">No expenses for today.</td></tr>
<% end %>
</tbody>
<tfoot>
<tr><th>Total</th><th><%= number_to_currency(@daily_expenses || 0, unit: "₹") %></th></tr>
</tfoot>
</table>
</section>
<!-- Week table -->
<section>
<h3 style="margin:0 0 8px 0; font-size:1rem; color:#374151;">This Week's Expenses</h3>
<table class="table" aria-describedby="week-expenses">
<thead>
<tr><th>Amount</th><th>Date</th></tr>
</thead>
<tbody>
<% (@weekly_expense_items || []).each do |expense| %>
<tr>
<td><%= number_to_currency(expense.amount, unit: "₹") %></td>
<td><%= expense.created_at.strftime("%d %b %Y") %></td>
</tr>
<% end %>
<% if (@weekly_expense_items || []).empty? %>
<tr><td colspan="2" style="padding:12px;color:var(--muted)">No expenses for this week.</td></tr>
<% end %>
</tbody>
<tfoot>
<tr><th>Total</th><th><%= number_to_currency(@weekly_expenses || 0, unit: "₹") %></th></tr>
</tfoot>
</table>
</section>
<!-- Month table -->
<section>
<h3 style="margin:0 0 8px 0; font-size:1rem; color:#374151;">This Month's Expenses</h3>
<table class="table" aria-describedby="month-expenses">
<thead>
<tr><th>Amount</th><th>Date</th></tr>
</thead>
<tbody>
<% (@monthly_expense_items || []).each do |expense| %>
<tr>
<td><%= number_to_currency(expense.amount, unit: "₹") %></td>
<td><%= expense.created_at.strftime("%d %b %Y") %></td>
</tr>
<% end %>
<% if (@monthly_expense_items || []).empty? %>
<tr><td colspan="2" style="padding:12px;color:var(--muted)">No expenses for this month.</td></tr>
<% end %>
</tbody>
<tfoot>
<tr><th>Total</th><th><%= number_to_currency(@monthly_expenses || 0, unit: "₹") %></th></tr>
</tfoot>
</table>
</section>
</div>
<!-- Chart.js (rounded line + subtle gradient) -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
(function(){
const labels = <%= raw((@chart_labels || []).to_json) %>;
const data = <%= raw((@chart_data || []).to_json) %>;
const ctx = document.getElementById('expensesChart').getContext('2d');
const gradient = ctx.createLinearGradient(0,0,0,120);
gradient.addColorStop(0, 'rgba(37,99,235,0.18)');
gradient.addColorStop(1, 'rgba(37,99,235,0)');
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Expenses',
data: data,
fill: true,
backgroundColor: gradient,
borderColor: 'rgba(37,99,235,1)',
tension: 0.42,
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: 'rgba(37,99,235,1)'
}]
},
options: {
maintainAspectRatio: false,
scales: {
x: { ticks: { color: '#374151' } },
y: {
beginAtZero: true,
ticks: {
callback: function(v){ return '₹' + v; },
color: '#374151'
},
grid: { color: 'rgba(15,23,42,0.04)' }
}
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(ctx){ return '₹' + ctx.formattedValue; }
}
}
}
}
});
})();
</script>
<% content_for :title, "Expense Report" %>

View File

@ -0,0 +1,11 @@
<p style="color: green"><%= notice %></p>
<%= render @expense %>
<div>
<%= link_to "Edit this expense", edit_expense_path(@expense) %> |
<%= link_to "Back to expenses", expenses_path %>
<%= button_to "Destroy this expense", @expense, method: :delete %>
</div>

View File

@ -0,0 +1 @@
json.partial! "expenses/expense", expense: @expense

View File

@ -0,0 +1,42 @@
<%= form_with(model: income) do |form| %>
<% if income.errors.any? %>
<div style="color: red">
<h2><%= pluralize(income.errors.count, "error") %> prohibited this income from being saved:</h2>
<ul>
<% income.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :amount, style: "display: block" %>
<%= form.text_field :amount %>
</div>
<div>
<%= form.label :date, style: "display: block" %>
<%= form.date_field :date %>
</div>
<div>
<%= form.label :category, style: "display: block" %>
<%= form.text_field :category %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.textarea :description %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>

View File

@ -0,0 +1,27 @@
<div id="<%= dom_id income %>">
<p>
<strong>Title:</strong>
<%= income.title %>
</p>
<p>
<strong>Amount:</strong>
<%= income.amount %>
</p>
<p>
<strong>Date:</strong>
<%= income.date %>
</p>
<p>
<strong>Category:</strong>
<%= income.category %>
</p>
<p>
<strong>Description:</strong>
<%= income.description %>
</p>
</div>

View File

@ -0,0 +1,2 @@
json.extract! income, :id, :title, :amount, :date, :category, :description, :created_at, :updated_at
json.url income_url(income, format: :json)

View File

@ -0,0 +1,12 @@
<% content_for :title, "Editing income" %>
<h1>Editing income</h1>
<%= render "form", income: @income %>
<br>
<div>
<%= link_to "Show this income", @income %> |
<%= link_to "Back to incomes", incomes_path %>
</div>

View File

@ -0,0 +1,103 @@
<p style="color: green"><%= notice %></p>
<% content_for :title, "Incomes" %>
<h1>Incomes</h1>
<div class="text-end mb-3">
<%= link_to "📊 Income Report", income_report_path, class: "btn btn-outline-primary" %>
</div>
<%= link_to "New income", new_income_path, class:"btn-income" %>
<div id="income">
<% @incomes.each do |income| %>
<div class="income-card-item">
<div class="amount"><%= number_to_currency(income.amount) %></div>
<div class="date"><%= income.created_at.strftime("%d %b %Y") %></div>
<%= link_to "Show this income", income, class: "show-link" %>
</div>
<% end %>
</div>
<style>
/* Container for all cards */
#income {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
margin-top: 20px;
}
/* Individual income card */
.income-card-item {
background: linear-gradient(135deg, #f0f8ff, #e6f7ff); /* soft gradient */
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
width: 250px;
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* Hover effect for card */
.income-card-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
/* Amount styling */
.income-card-item .amount {
font-size: 1.4rem;
font-weight: 600;
color: #007bff;
margin-bottom: 10px;
}
/* Date styling */
.income-card-item .date {
font-size: 0.9rem;
color: #555555;
margin-bottom: 15px;
}
/* Show link styling */
.income-card-item .show-link {
align-self: flex-start;
background-color: #28a745;
color: white;
padding: 6px 12px;
border-radius: 6px;
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.income-card-item .show-link:hover {
background-color: #1e7e34;
transform: scale(1.05);
}
.btn-income {
display: inline-block;
background: linear-gradient(135deg, #6a11cb, #2575fc);
color: #fff;
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
text-decoration: none;
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.btn-income:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
background: linear-gradient(135deg, #2575fc, #6a11cb);
}
</style>

View File

@ -0,0 +1 @@
json.array! @incomes, partial: "incomes/income", as: :income

View File

@ -0,0 +1,11 @@
<% content_for :title, "New income" %>
<h1>New income</h1>
<%= render "form", income: @income %>
<br>
<div>
<%= link_to "Back to incomes", incomes_path %>
</div>

View File

@ -0,0 +1,273 @@
<% content_for :title, "Income Report" %>
<style>
:root{
--card-bg: #ffffff;
--muted: #6b7280;
--accent: #2563eb;
--radius: 10px;
--shadow: 0 6px 20px rgba(16,24,40,0.06);
--gap: 18px;
font-family: "Inter", "Poppins", system-ui;
}
.er-container {
max-width: 920px;
margin: 28px auto;
padding: 18px;
color: #111827;
}
.er-header {
display:flex;
align-items:center;
justify-content:space-between;
gap:12px;
margin-bottom: var(--gap);
}
.er-title {
color: #fff;
font-size:1.4rem;
font-weight:600;
margin:0;
}
.er-actions { display:flex; gap:10px; align-items:center; }
.btn-simple {
background: var(--accent);
color:#fff;
padding:8px 12px;
border-radius:8px;
text-decoration:none;
font-weight:600;
font-size:0.95rem;
box-shadow: 0 6px 16px rgba(37,99,235,0.12);
transition: transform .12s ease, box-shadow .12s ease;
}
.btn-simple:hover { transform: translateY(-3px); box-shadow: 0 10px 26px rgba(37,99,235,0.16); }
.summary-row {
display: flex;
gap: 16px;
margin-bottom: 22px;
}
.card {
flex: 1;
padding: 16px;
border-radius: var(--radius);
background: #87ceeb;
border: 1px solid var(--border);
backdrop-filter: blur(12px);
box-shadow: var(--shadowG3);
transition: .25s;
}
.card:hover {
transform: translateY(-6px) scale(1.02);
box-shadow: 0 12px 36px rgba(168,85,247,0.75);
}
.card small {
color: var(--muted);
font-weight: 500;
}
.card .value {
font-size: 1.4rem;
font-weight: 700;
margin-top: 6px;
}
.chart-wrap {
background: var(--card-bg);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding:12px;
margin-bottom: var(--gap);
border: 1px solid rgba(15,23,42,0.04);
}
.table {
width:100%;
border-collapse:collapse;
margin-bottom:20px;
background: transparent;
}
.table thead th {
text-align:left; font-size:0.9rem; color:var(--muted); padding:10px 12px;
border-bottom: 1px solid rgba(15,23,42,0.06);
}
.table tbody tr { background: #fff; border-radius:8px; }
.table td {
padding:11px 12px;
font-size:0.95rem;
vertical-align:middle;
border-bottom: 1px solid rgba(15,23,42,0.03);
}
.table tfoot th {
padding:10px 12px;
text-align:left;
font-weight:700;
border-top:1px solid rgba(15,23,42,0.06);
}
@media (max-width:720px){
.summary-row { flex-direction:column; }
.er-header { flex-direction:column; align-items:flex-start; gap:10px; }
}
</style>
<div class="er-container">
<div class="er-header">
<h1 class="er-title">INCOME REPORT</h1>
<div class="er-actions">
<%= link_to "New Income", new_income_path, class: "btn-simple" %>
</div>
</div>
<!-- Summary -->
<div class="summary-row">
<div class="card">
<small>Today's Income</small>
<div class="value"><%= number_to_currency(@daily_income || 0, unit: "₹") %></div>
</div>
<div class="card">
<small>This Week</small>
<div class="value"><%= number_to_currency(@weekly_income || 0, unit: "₹") %></div>
</div>
<div class="card">
<small>This Month</small>
<div class="value"><%= number_to_currency(@monthly_income || 0, unit: "₹") %></div>
</div>
</div>
<!-- Chart -->
<div class="chart-wrap">
<canvas id="incomeChart" height="120"></canvas>
</div>
<!-- Today's table -->
<section>
<h3 style="margin:0 0 8px 0; font-size:1rem; color:#374151;">Today's Income</h3>
<table class="table">
<thead>
<tr><th>Amount</th><th>Date</th></tr>
</thead>
<tbody>
<% (@daily_income_items || []).each do |income| %>
<tr>
<td><%= number_to_currency(income.amount, unit: "₹") %></td>
<td><%= income.created_at.strftime("%d %b %Y") %></td>
</tr>
<% end %>
<% if (@daily_income_items || []).empty? %>
<tr><td colspan="2" style="padding:12px;color:var(--muted)">No income for today.</td></tr>
<% end %>
</tbody>
<tfoot>
<tr><th>Total</th><th><%= number_to_currency(@daily_income || 0, unit: "₹") %></th></tr>
</tfoot>
</table>
</section>
<!-- Week table -->
<section>
<h3 style="margin:0 0 8px 0; font-size:1rem; color:#374151;">This Week's Income</h3>
<table class="table">
<thead>
<tr><th>Amount</th><th>Date</th></tr>
</thead>
<tbody>
<% (@weekly_income_items || []).each do |income| %>
<tr>
<td><%= number_to_currency(income.amount, unit: "₹") %></td>
<td><%= income.created_at.strftime("%d %b %Y") %></td>
</tr>
<% end %>
<% if (@weekly_income_items || []).empty? %>
<tr><td colspan="2" style="padding:12px;color:var(--muted)">No income for this week.</td></tr>
<% end %>
</tbody>
<tfoot>
<tr><th>Total</th><th><%= number_to_currency(@weekly_income || 0, unit: "₹") %></th></tr>
</tfoot>
</table>
</section>
<!-- Month table -->
<section>
<h3 style="margin:0 0 8px 0; font-size:1rem; color:#374151;">This Month's Income</h3>
<table class="table">
<thead>
<tr><th>Amount</th><th>Date</th></tr>
</thead>
<tbody>
<% (@monthly_income_items || []).each do |income| %>
<tr>
<td><%= number_to_currency(income.amount, unit: "₹") %></td>
<td><%= income.created_at.strftime("%d %b %Y") %></td>
</tr>
<% end %>
<% if (@monthly_income_items || []).empty? %>
<tr><td colspan="2" style="padding:12px;color:var(--muted)">No income for this month.</td></tr>
<% end %>
</tbody>
<tfoot>
<tr><th>Total</th><th><%= number_to_currency(@monthly_income || 0, unit: "₹") %></th></tr>
</tfoot>
</table>
</section>
</div>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
(function(){
const labels = <%= raw((@chart_labels || []).to_json) %>;
const data = <%= raw((@chart_data || []).to_json) %>;
const ctx = document.getElementById('incomeChart').getContext('2d');
const gradient = ctx.createLinearGradient(0,0,0,120);
gradient.addColorStop(0, 'rgba(37,99,235,0.18)');
gradient.addColorStop(1, 'rgba(37,99,235,0)');
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Income',
data: data,
fill: true,
backgroundColor: gradient,
borderColor: 'rgba(37,99,235,1)',
tension: 0.42,
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: 'rgba(37,99,235,1)'
}]
},
options: {
maintainAspectRatio: false,
scales: {
x: { ticks: { color: '#374151' } },
y: {
beginAtZero: true,
ticks: { callback: v => '₹' + v, color: '#374151' },
grid: { color: 'rgba(15,23,42,0.04)' }
}
},
plugins: {
legend: { display: false },
tooltip: { callbacks: { label: ctx => '₹' + ctx.formattedValue } }
}
}
});
})();
</script>

View File

@ -0,0 +1,10 @@
<p style="color: green"><%= notice %></p>
<%= render @income %>
<div>
<%= link_to "Edit this income", edit_income_path(@income) %> |
<%= link_to "Back to incomes", incomes_path %>
<%= button_to "Destroy this income", @income, method: :delete %>
</div>

View File

@ -0,0 +1 @@
json.partial! "incomes/income", income: @income

View File

@ -0,0 +1,10 @@
<h2>Contact Us</h2>
<p>If you have any questions or need assistance, please feel free to reach out to us:</p>
<ul>
<li>Email: <a href="mailto:info@badarmadeena.org">info@badarmadeena.org</a></li>
<li>Phone: +91 9747111705</li>
<li>Phone: +91 8592080609</li>
<li>Website: <a href="https://badarmadeena.org">badarmadeena.org</a></li>
<li>Address: Badar Madeena Educational Trust, Kerala, India</li>
</ul>

View File

@ -1,229 +1,252 @@
<style> <style>
html, body { /* ---------------- Body & Layout ---------------- */
html, body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
}
body {
background: linear-gradient(135deg, #e3f2fd, #e8f5e9);
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
color: #2c3e50; /* Dark gradient background */
display: flex; background: linear-gradient(135deg, #0f1724, #1e293b); /* deep navy/charcoal */
flex-direction: column; color: #e6eef6;
} }
.page-wrapper { .page-wrapper {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between; /* space out header and content */
min-height: 100vh; min-height: 100vh;
}
.container {
padding: 0 20px;
max-width: 1900px;
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; /* center vertically */ justify-content: flex-start;
} }
.text-center.my-5 { .container {
margin-top: 0 !important; max-width: 1900px;
margin-bottom: 1.5rem !important; margin: 0 auto;
} padding: 20px;
flex: 1;
}
/* Header Section */ /* ---------------- Header ---------------- */
.page-header { .page-header {
text-align: center; text-align: center;
} margin-bottom: 15px;
}
.page-header h1 { .page-header h1 {
font-weight: 700; font-weight: 700;
color: #1e88e5; font-size: 3rem;
margin-bottom: 0px;
background: linear-gradient(90deg, #1e88e5, #43a047, #1e88e5); background: linear-gradient(90deg, #1e88e5, #43a047, #1e88e5);
background-size: 200% auto; background-size: 200% auto;
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
animation: gradient-move 3s linear infinite; animation: gradient-move 3s linear infinite;
margin-bottom: 5px;
} }
@keyframes gradient-move { @keyframes gradient-move {
0% { 0% { background-position: 0% center; }
background-position: 0% center; 100% { background-position: 200% center; }
}
100% {
background-position: 200% center;
}
} }
.logo-img {
.logo-img {
width: 100px; width: 100px;
height: 100px; height: 100px;
object-fit: cover; object-fit: cover;
border-radius: 50%; border-radius: 50%;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 10px rgba(0,0,0,0.15);
} margin-bottom: 10px;
}
.divider { .divider {
width: 70px; width: 80px;
height: 4px; height: 4px;
background: linear-gradient(to right, #1e88e5, #43a047); background: linear-gradient(to right, #1e88e5, #43a047);
margin: 15px auto; margin: 15px auto;
border-radius: 3px; border-radius: 3px;
} }
/* Card Styles */ /* ---------------- Stats Cards ---------------- */
.glass-card { .stats-row {
background: rgba(255, 255, 255, 0.9); display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin-bottom: 24px;
}
.stat-card {
flex: 0 0 auto;
width: 140px; /* compact cards */
margin: 6px;
padding: 12px 16px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 12px;
box-shadow: 0 6px 18px rgba(2,6,23,0.6);
transition: transform 0.18s ease, box-shadow 0.18s ease;
text-align: center;
}
.stat-card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 22px rgba(2,6,23,0.7);
}
.stat-label {
font-weight: 600;
color: rgba(230,238,246,0.8);
margin-bottom: 6px;
font-size: 0.8rem;
}
.stat-value {
font-size: 1.4rem; /* smaller count */
font-weight: 700;
color: #7dd3fc; /* soft cyan */
}
/* ---------------- Glass Cards ---------------- */
.glass-card {
background: rgba(255,255,255,0.9);
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
border-radius: 18px; border-radius: 18px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); box-shadow: 0 10px 25px rgba(0,0,0,0.1);
padding: 25px;
transition: 0.3s ease; transition: 0.3s ease;
height: 100%; width: 100%;
} }
.glass-card:hover { .glass-card:hover {
transform: translateY(-3px); transform: translateY(-3px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15); box-shadow: 0 12px 30px rgba(0,0,0,0.15);
} }
.section-title { /* ---------------- Section Titles ---------------- */
.section-title {
font-weight: 700; font-weight: 700;
font-size: 1.5rem;
margin-bottom: 20px;
text-align: center; text-align: center;
color: #1565c0; }
margin-bottom: 15px;
}
.item-card { /* ---------------- Item Cards ---------------- */
.item-card {
border-radius: 15px; border-radius: 15px;
background: #ffffff; background: #ffffff;
transition: all 0.3s ease;
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
} padding: 15px;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.item-card:hover { .item-card:hover {
transform: scale(1.03);
background: #f9f9f9; background: #f9f9f9;
transform: scale(1.02); box-shadow: 0 8px 20px rgba(0,0,0,0.1);
} }
.item-title { .item-title {
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 600; font-weight: 600;
color: #0d47a1; color: #0d47a1;
} margin-bottom: 5px;
}
.item-desc { .item-desc {
color: #37474f;
font-size: 0.95rem; font-size: 0.95rem;
} color: #37474f;
margin-bottom: 10px;
}
.item-footer { .item-footer {
border-top: 1px solid #e3f2fd;
padding-top: 10px;
text-align: right; text-align: right;
} }
.btn-custom { /* ---------------- Buttons ---------------- */
.btn-custom {
border-radius: 25px; border-radius: 25px;
font-weight: 600; font-weight: 600;
padding: 7px 18px; padding: 7px 18px;
transition: 0.3s ease; transition: 0.3s ease;
} }
.btn-primary-custom { .btn-primary-custom { background-color: #1e88e5; color: #fff; }
background-color: #1e88e5; .btn-primary-custom:hover { background-color: #1565c0; }
color: #fff;
}
.btn-primary-custom:hover { .btn-success-custom { background-color: #43a047; color: #fff; }
background-color: #1565c0; .btn-success-custom:hover { background-color: #2e7d32; }
}
.btn-success-custom { /* ---------------- Ziyara Section ---------------- */
background-color: #43a047;
color: #fff;
}
.btn-success-custom:hover {
background-color: #2e7d32;
}
footer {
background: rgba(255, 255, 255, 0.7);
padding: 12px;
text-align: center;
font-size: 0.9rem;
color: #555;
backdrop-filter: blur(8px);
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
@media (max-width: 992px) {
.container {
padding: 20px;
}
}
/* --- Ziyara Section Styling --- */
.ziyara-card { .ziyara-card {
border-top: 5px solid #fbc02d; border-top: 5px solid #fbc02d;
background: rgba(255, 255, 240, 0.95); background: rgba(255,255,240,0.95);
} }
.ziyara-title { .ziyara-title {
color: #f9a825;
background: linear-gradient(90deg, #fdd835, #fbc02d, #f57f17); background: linear-gradient(90deg, #fdd835, #fbc02d, #f57f17);
background-size: 200% auto;
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
animation: gradient-move 4s linear infinite; animation: gradient-move 4s linear infinite;
} }
.ziyara-item { /* ---------------- Responsive ---------------- */
border-left: 4px solid #fbc02d; @media (max-width: 992px) {
transition: all 0.3s ease; .stats-row { flex-direction: column; align-items: center; }
} }
.ziyara-item:hover {
background: #fff8e1;
box-shadow: 0 8px 20px rgba(251, 192, 45, 0.25);
transform: translateY(-4px);
}
</style> </style>
<div class="page-wrapper"> <div class="page-wrapper">
<div class="container text-center"> <div class="container">
<!-- Logo & Header --> <!-- Header -->
<div class="page-header mb-4 d-flex align-items-center justify-content-center flex-wrap"> <div class="page-header d-flex flex-column align-items-center">
<%= image_tag("unnamed.jpg", alt: "Logo", class: "logo-img me-3") %> <%= image_tag("unnamed.jpg", alt: "Logo", class: "logo-img") %>
<h1>BADAR MADEENA CULTURAL CENTER <h1>BADAR MADEENA CULTURAL CENTER AND CHARITY TRUST</h1>
AND CHARITY TRUST</h1> <div class="divider"></div>
</div>
<!-- Stats Row -->
<div class="stats-row">
<div class="stat-card">
<div class="stat-label">INSTITUTION</div>
<div class="stat-value"><%= @institution_count || 0 %></div>
</div>
<div class="stat-card">
<div class="stat-label">PROGRAMS</div>
<div class="stat-value"><%= @program_count || 0 %></div>
</div>
<div class="stat-card">
<div class="stat-label">STUDENTS</div>
<div class="stat-value"><%= @student_count || 0 %></div>
</div>
<div class="stat-card">
<div class="stat-label">ZIYARA</div>
<div class="stat-value"><%= @ziyara_count || 0 %></div>
</div>
<div class="stat-card">
<div class="stat-label">TRADITIONAL RECORDS</div>
<div class="stat-value"><%= @exclusive_traditional_records_count || 0 %></div>
</div>
</div> </div>
<div class="divider"></div>
<!-- Two Sections Side by Side -->
<div class="row g-4 justify-content-center align-items-stretch"> <!-- Two Column Sections -->
<!-- Institutions Section --> <div class="row g-4">
<!-- Institutions -->
<div class="col-lg-6 d-flex"> <div class="col-lg-6 d-flex">
<div class="glass-card p-4 w-100"> <div class="glass-card">
<h3 class="section-title text-primary">🏫 Institutions</h3> <h3 class="section-title text-primary">🏫 Institutions</h3>
<% if @institutions.present? %> <% if @institutions.present? %>
<div class="row g-3"> <div class="row g-3">
<% @institutions.each do |institution| %> <% @institutions.each do |institution| %>
<div class="col-12"> <div class="col-12">
<div class="item-card p-3 shadow-sm"> <div class="item-card">
<div class="item-title"><%= institution.name %></div> <div class="item-title"><%= institution.name %></div>
<% if institution.respond_to?(:description) %> <% if institution.respond_to?(:description) %>
<p class="item-desc mb-2"><%= institution.description.truncate(100) %></p> <p class="item-desc"><%= institution.description.truncate(100) %></p>
<% end %> <% end %>
<div class="item-footer"> <div class="item-footer d-flex justify-content-end gap-2">
<%= link_to "View", institution, class: "btn btn-primary-custom btn-sm btn-custom" %> <%= link_to "Students", institution_students_path(institution), class: "btn btn-success-custom btn-sm btn-custom" %>
</div> </div>
</div> </div>
</div> </div>
@ -233,29 +256,28 @@
<p class="item-desc text-center">No institutions added yet.</p> <p class="item-desc text-center">No institutions added yet.</p>
<% end %> <% end %>
<div class="text-center mt-3"> <div class="text-center mt-3">
<%= link_to " New Institution", new_institution_path, class: "btn btn-success-custom btn-custom" %> <%= link_to " New Institution", new_institution_path, class: "btn btn-success-custom btn-custom me-2" %>
<%= link_to "📚 All Students", students_path, class: "btn btn-primary-custom btn-custom" %>
</div> </div>
</div> </div>
</div> </div>
<!-- Programs Section --> <!-- Programs -->
<div class="col-lg-6 d-flex"> <div class="col-lg-6 d-flex">
<div class="glass-card p-4 w-100"> <div class="glass-card">
<h3 class="section-title text-success">🎓 Programs</h3> <h3 class="section-title text-success">🎓 Programs</h3>
<% if @programs.present? %> <% if @programs.present? %>
<div class="row g-3"> <div class="row g-3">
<% @programs.each do |program| %> <% @programs.each do |program| %>
<div class="col-12"> <div class="col-12">
<div class="item-card p-3 shadow-sm"> <div class="item-card">
<div class="item-title"><%= program.name %></div> <div class="item-title"><%= program.name %></div>
<p class="item-desc mb-2">
<% if program.respond_to?(:date) %> <% if program.respond_to?(:date) %>
<strong>Date:</strong> <%= program.date %><br> <p class="item-desc"><strong>Date:</strong> <%= program.date %></p>
<% end %> <% end %>
<% if program.respond_to?(:leadingPerson) %> <% if program.respond_to?(:leadingPerson) %>
<strong>Lead:</strong> <%= program.leadingPerson %> <p class="item-desc"><strong>Lead:</strong> <%= program.leadingPerson %></p>
<% end %> <% end %>
</p>
<div class="item-footer"> <div class="item-footer">
<%= link_to "View", program, class: "btn btn-success-custom btn-sm btn-custom" %> <%= link_to "View", program, class: "btn btn-success-custom btn-sm btn-custom" %>
</div> </div>
@ -273,25 +295,22 @@
</div> </div>
</div> </div>
<!-- Ziyara & Exclusive Traditional Records Side-by-side -->
<div class="row mt-4 g-4 justify-content-center">
<!-- Ziyara Section --> <div class="col-lg-5 d-flex">
<div class="row mt-4 justify-content-center"> <div class="glass-card ziyara-card w-100">
<div class="col-lg-10 d-flex">
<div class="glass-card ziyara-card p-4 w-100">
<h3 class="section-title ziyara-title">🕌 Ziyara</h3> <h3 class="section-title ziyara-title">🕌 Ziyara</h3>
<% if @ziyaras.present? %> <% if @ziyaras.present? %>
<div class="row g-3"> <div class="row g-3">
<% @ziyaras.each do |ziyara| %> <% @ziyaras.each do |ziyara| %>
<div class="col-md-6 col-lg-4"> <div class="col-12">
<div class="item-card ziyara-item p-3 shadow-sm"> <div class="item-card">
<div class="item-title"><%= ziyara.name %></div> <div class="item-title"><%= ziyara.name %></div>
<% if ziyara.respond_to?(:location) %> <% if ziyara.respond_to?(:location) %>
<p class="item-desc mb-1"><strong>📍 Location:</strong> <%= ziyara.location %></p> <p class="item-desc"><strong>📍 Location:</strong> <%= ziyara.location %></p>
<% end %> <% end %>
<% if ziyara.respond_to?(:date) %> <% if ziyara.respond_to?(:date) %>
<p class="item-desc mb-1"><strong>📅 Date:</strong> <%= ziyara.date %></p> <p class="item-desc"><strong>📅 Date:</strong> <%= ziyara.date %></p>
<% end %> <% end %>
<div class="item-footer"> <div class="item-footer">
<%= link_to "View", ziyara, class: "btn btn-primary-custom btn-sm btn-custom" %> <%= link_to "View", ziyara, class: "btn btn-primary-custom btn-sm btn-custom" %>
@ -303,13 +322,199 @@
<% else %> <% else %>
<p class="item-desc text-center">No Ziyara records yet.</p> <p class="item-desc text-center">No Ziyara records yet.</p>
<% end %> <% end %>
<div class="text-center mt-3"> <div class="text-center mt-3">
<%= link_to " New Ziyara", new_ziyara_path, class: "btn btn-success-custom btn-custom" %> <%= link_to " New Ziyara", new_ziyara_path, class: "btn btn-success-custom btn-custom" %>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-5 d-flex">
<div class="glass-card w-100">
<h3 class="section-title text-secondary">📜 Exclusive Traditional Records</h3>
<% if @exclusive_traditional_records.present? %>
<div class="row g-3">
<% @exclusive_traditional_records.each do |record| %>
<div class="col-12">
<div class="item-card">
<div class="item-title"><%= record.name if record.respond_to?(:name) %></div>
<% if record.respond_to?(:description) %>
<p class="item-desc"><%= record.description.truncate(100) %></p>
<% end %>
<div class="item-footer">
<%= link_to "View", record, class: "btn btn-primary-custom btn-sm btn-custom" %>
</div>
</div>
</div>
<% end %>
</div>
<% else %>
<p class="item-desc text-center">No exclusive traditional records yet.</p>
<% end %>
<div class="text-center mt-3">
<%= link_to " New Record", new_exclusive_traditional_record_path, class: "btn btn-success-custom btn-custom" %>
</div>
</div>
</div>
</div>
<% content_for :title, "Expenses" %>
<style>
/* Main card container */
.expense-card {
background: #ffffff;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
padding: 20px 25px;
max-width: 420px;
margin: 30px auto;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
/* Hover effect */
.expense-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
/* Card header */
.expense-card h1 {
font-size: 1.7rem;
margin-bottom: 15px;
color: #333333;
text-align: center;
font-weight: 600;
}
/* Flash notice */
.expense-card .flash-notice {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
padding: 10px 14px;
border-radius: 8px;
font-size: 0.95rem;
margin-bottom: 15px;
text-align: center;
}
/* Button styling */
.expense-card .btn {
display: block;
width: 100%;
text-align: center;
background-color: #007bff;
color: #fff;
padding: 10px 0;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
text-decoration: none;
transition: background-color 0.3s ease, transform 0.2s ease;
}
/* Button hover */
.expense-card .btn:hover {
background-color: #0056b3;
transform: scale(1.03);
}
/* Main card container */
.income-card {
background: #ffffff;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
padding: 20px 25px;
max-width: 420px;
margin: 30px auto;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
/* Hover effect */
.income-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
/* Card header */
.income-card h1 {
font-size: 1.7rem;
margin-bottom: 15px;
color: #333333;
text-align: center;
font-weight: 600;
}
/* Flash notice */
.income-card .flash-notice {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
padding: 10px 14px;
border-radius: 8px;
font-size: 0.95rem;
margin-bottom: 15px;
text-align: center;
}
/* Button styling */
.income-card .btn {
display: block;
width: 100%;
text-align: center;
background-color: #007bff;
color: #fff;
padding: 10px 0;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
text-decoration: none;
transition: background-color 0.3s ease, transform 0.2s ease;
}
/* Button hover */
.income-card .btn:hover {
background-color: #0056b3;
transform: scale(1.03);
}
</style>
<div class="row mt-4 g-4 justify-content-center">
<!-- Expense Card -->
<div class="col-lg-5 d-flex">
<div class="expense-card w-100">
<% if notice.present? %>
<div class="flash-notice">
<%= notice %>
</div>
<% end %>
<h1>📊 Expenses</h1>
<%= link_to "Expenses List", expenses_path, class: "btn" %>
</div>
</div>
<!-- Income Card -->
<div class="col-lg-5 d-flex">
<div class="income-card w-100">
<% if notice.present? %>
<div class="flash-notice">
<%= notice %>
</div>
<% end %>
<h1>💰 Incomes</h1>
<%= link_to "Incomes List", incomes_path, class: "btn" %>
</div>
</div>
</div> </div>
</div> </div> <!-- container -->
</div> <!-- page-wrapper -->

View File

@ -8,7 +8,6 @@
<div class="card bg-white shadow p-4 mb-4"> <div class="card bg-white shadow p-4 mb-4">
<div class="card-body"> <div class="card-body">
<h2 class="card-title mb-3 text-primary"><%= @institution.name %></h2> <h2 class="card-title mb-3 text-primary"><%= @institution.name %></h2>
<p class="card-text mb-2"><strong>ID:</strong> <%= @institution.id %></p>
<%# Add more institution details here if needed %> <%# Add more institution details here if needed %>
<div class="mb-3"> <div class="mb-3">
<%= link_to "Edit Institution", edit_institution_path(@institution), class: "btn btn-secondary me-2" %> <%= link_to "Edit Institution", edit_institution_path(@institution), class: "btn btn-secondary me-2" %>
@ -29,7 +28,6 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title text-info"><%= student.first_name %> <%= student.last_name %></h5> <h5 class="card-title text-info"><%= student.first_name %> <%= student.last_name %></h5>
<p class="card-text mb-1"><strong>Email:</strong> <%= student.email %></p> <p class="card-text mb-1"><strong>Email:</strong> <%= student.email %></p>
<p class="card-text mb-1"><strong>ID:</strong> <%= student.id %></p>
</div> </div>
<div class="card-footer bg-transparent border-0 d-flex justify-content-end"> <div class="card-footer bg-transparent border-0 d-flex justify-content-end">
<%= link_to "Show", institution_student_path(@institution, student), class: "btn btn-primary btn-sm me-2" %> <%= link_to "Show", institution_student_path(@institution, student), class: "btn btn-primary btn-sm me-2" %>

View File

@ -19,11 +19,14 @@
<!-- Bootstrap CSS for modern UI --> <!-- Bootstrap CSS for modern UI -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style> <style>
body { body {
background-color: #181a1b !important; background-color: #181a1b !important;
color: #e0e0e0 !important; color: #e0e0e0 !important;
} }
.bg-white { .bg-white {
background-color: #23272b !important; background-color: #23272b !important;
color: #e0e0e0 !important; color: #e0e0e0 !important;
@ -73,7 +76,7 @@
<!-- Logo & Brand --> <!-- Logo & Brand -->
<%= link_to root_path, class: "navbar-brand d-flex align-items-center" do %> <%= link_to root_path, class: "navbar-brand d-flex align-items-center" do %>
<%= image_tag("unnamed.jpg", alt: "Logo", class: "nav-logo me-2") %> <%= image_tag("unnamed.jpg", alt: "Logo", class: "nav-logo me-2") %>
<span>Badar Madeena Trust</span> <span>Badar Madeena pullarikode</span>
<% end %> <% end %>
<!-- Toggler (for mobile) --> <!-- Toggler (for mobile) -->
@ -98,7 +101,13 @@
<%= link_to "🕌 Ziyara", ziyaras_path, class: "nav-link" %> <%= link_to "🕌 Ziyara", ziyaras_path, class: "nav-link" %>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<%= link_to "📞 Contact", "#", class: "nav-link" %> <%= link_to "👩‍🎓 Students", students_path, class: "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "📊 Expenses", expenses_path, class: "nav-link" %>
</li>
<li class="nav-item">
<%= link_to "📞 Contact", contact_path, class: "nav-link" %>
</li> </li>
</ul> </ul>
</div> </div>
@ -117,7 +126,7 @@
.glass-navbar .navbar-brand { .glass-navbar .navbar-brand {
font-weight: 700; font-weight: 700;
font-size: 1.1rem; font-size: 1.9rem;
color: #1e88e5; color: #1e88e5;
background: linear-gradient(90deg, #1e88e5, #43a047); background: linear-gradient(90deg, #1e88e5, #43a047);
-webkit-background-clip: text; -webkit-background-clip: text;
@ -136,7 +145,7 @@
.navbar-nav .nav-link { .navbar-nav .nav-link {
font-weight: 600; font-weight: 600;
color: #2c3e50 !important; color: #91e59b !important;
padding: 8px 18px; padding: 8px 18px;
border-radius: 25px; border-radius: 25px;
transition: all 0.3s ease; transition: all 0.3s ease;
@ -179,10 +188,11 @@
<% if notice %> <% if notice %>
<div class="alert alert-success"><%= notice %></div> <div class="alert alert-success"><%= notice %></div>
<% end %> <% end %>
<%= yield %>
<% if alert %> <% if alert %>
<div class="alert alert-danger"><%= alert %></div> <div class="alert alert-danger"><%= alert %></div>
<% end %> <% end %>
<%= yield %>
</div> </div>
<footer class="footer text-center mt-5 mb-3 text-muted"> <footer class="footer text-center mt-5 mb-3 text-muted">
&copy; <%= Time.current.year %> Badar Madeena &copy; <%= Time.current.year %> Badar Madeena

View File

@ -1,6 +1,6 @@
<% institution = @institution || student.institution %> <% institution = @institution || student.institution %>
<% form_url = student.persisted? ? institution_student_path(institution, student) : institution_students_path(institution) %> <% form_url = student.persisted? ? institution_student_path(institution, student) : institution_students_path(institution) %>
<%= form_with(model: student, url: form_url) do |form| %> <%= form_with(model: student, url: form_url, remote: true) do |form| %>
<% if student.errors.any? %> <% if student.errors.any? %>
<div style="color: red"> <div style="color: red">
<h2><%= pluralize(student.errors.count, "error") %> prohibited this student from being saved:</h2> <h2><%= pluralize(student.errors.count, "error") %> prohibited this student from being saved:</h2>
@ -43,11 +43,10 @@
<%= form.number_field :age %> <%= form.number_field :age %>
</div> </div>
<div class="field">
<div> <%= form.label :photo, "Upload Photo", style: "display: block" %>
<%= form.label :institution_id, style: "display: block" %> <%= form.file_field :photo, accept: "image/*" %>
<%= form.text_field :institution_id %> </div>
</div>
<div> <div>
<%= form.submit %> <%= form.submit %>

View File

@ -1,37 +1,123 @@
<div id="<%= dom_id student %>"> <div class="student-card" id="<%= dom_id student %>">
<p> <div class="student-header">
<strong>First name:</strong> <div class="student-photo">
<%= student.first_name %> <% if student.photo.attached? %>
</p> <%# Fallback for image processing errors %>
<% begin %>
<p> <%= image_tag url_for(student.photo.variant(resize_to_limit: [120, 120]).processed), alt: student.first_name %>
<strong>Last name:</strong> <% rescue => e %>
<%= student.last_name %> <%= image_tag url_for(student.photo), alt: student.first_name %>
</p> <% end %>
<% else %>
<p> <span class="placeholder">No Photo</span>
<strong>Email:</strong> <% end %>
<%= student.email %> </div>
</p> <div class="student-name">
<h3><%= student.first_name %> <%= student.last_name %></h3>
<p> <% if student.institution.present? %>
<strong>Institution:</strong> <%= link_to student.institution.name, institution_path(student.institution), class: "badge badge-primary" %>
<%= student.institution_id %> <% else %>
</p> <span class="badge badge-secondary">No institution</span>
<p> <% end %>
<strong>phone_number:</strong> <p class="student-email"><%= student.email %></p>
<%= student.phone_number %> </div>
</p> </div>
<p>
<strong>Place:</strong>
<%= student.place %>
</p>
<p>
<strong>Age:</strong>
<%= student.age %>
</p>
<div class="student-info">
<div class="info-item"><strong>Phone:</strong> <%= student.phone_number %></div>
<div class="info-item"><strong>Place:</strong> <%= student.place %></div>
<div class="info-item"><strong>Age:</strong> <%= student.age %></div>
</div>
</div> </div>
<style>
.student-card {
background: linear-gradient(135deg, #ffffff, #f9fafb);
border-radius: 16px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
max-width: 500px;
}
.student-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 32px rgba(0,0,0,0.15);
}
.student-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}
.student-photo {
width: 120px;
height: 120px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
background: #e5e7eb;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
color: #6b7280;
text-align: center;
}
.student-photo img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
.student-name h3 {
margin: 0 0 4px 0;
font-size: 1.5rem;
font-weight: 700;
color: #111827;
}
.student-name .badge {
margin-top: 6px;
}
.student-name .student-email {
font-size: 0.9rem;
color: #6b7280;
margin-top: 4px;
}
.student-info {
display: flex;
gap: 24px;
flex-wrap: wrap;
}
.info-item {
font-size: 0.95rem;
color: #374151;
}
.badge {
display: inline-block;
padding: 5px 12px;
border-radius: 999px;
font-size: 0.75rem;
color: #fff;
text-decoration: none;
}
.badge-primary { background: #2563eb; }
.badge-secondary { background: #6b7280; }
.placeholder {
font-size: 0.85rem;
color: #6b7280;
text-align: center;
}
</style>

View File

@ -1,2 +1,10 @@
json.extract! student, :id, :first_name, :last_name, :email, :institution_id, :created_at, :updated_at json.extract! student, :id, :first_name, :last_name, :email, :institution_id, :created_at, :updated_at
json.url student_url(student, format: :json) json.url student_url(student, format: :json)
// list refresh ചെയ്യുക
document.getElementById("students-list").innerHTML = "<%= j(render @students) %>";
// form clear ചെയ്യുക
document.getElementById("student-form").innerHTML = "<%= j(render 'form', student: Student.new) %>";
// success alert
alert("Student added successfully (without page reload!)");

View File

@ -1,16 +1,129 @@
<p style="color: green"><%= notice %></p> <p class="text-success text-center"><%= notice %></p>
<% content_for :title, "Students" %> <% content_for :title, "Students" %>
<h1>Students</h1> <style>
.students-container {
max-width: 900px;
margin: 25px auto;
padding: 10px;
font-family: "Poppins", sans-serif;
}
<div id="students"> .page-header {
<% @students.each do |student| %> display: flex;
<%= render student %> align-items: center;
<p> justify-content: space-between;
<%= link_to "Show this student", institution_student_path(@institution, student) %> margin-bottom: 18px;
</p> }
.page-header h1 {
font-size: 1.8rem;
font-weight: 600;
margin: 0;
}
.btn-primary {
background: #2563eb;
color: #fff;
padding: 10px 14px;
text-decoration: none;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(37,99,235,0.25);
font-weight: 600;
transition: 0.2s ease;
}
.btn-primary:hover {
background: #1e4fc4;
transform: translateY(-2px);
}
.student-card {
background: #fff;
border-radius: 10px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 4px 14px rgba(0,0,0,0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.student-info h3 {
font-size: 1.1rem;
margin: 0;
font-weight: 600;
color: #111827;
}
.student-info p {
margin: 3px 0 0;
color: #6b7280;
font-size: 0.9rem;
}
.btn-small {
background: #10b981;
color: #fff;
padding: 8px 12px;
text-decoration: none;
border-radius: 6px;
font-weight: 600;
transition: 0.2s ease;
}
.btn-small:hover {
background: #0a8d63;
}
.badge {
display: inline-block;
padding: 4px 8px;
border-radius: 999px;
font-size: 0.8rem;
color: #fff;
text-decoration: none;
}
.badge-primary { background: #2563eb; }
.badge-secondary { background: #6b7280; }
</style>
<div class="students-container">
<div class="page-header">
<h1>Students</h1>
<% if @institution.present? %>
<%= link_to " New Student", new_institution_student_path(@institution), class: "btn-primary" %>
<% else %>
<%= link_to " New Student", new_institution_path, class: "btn-primary" %>
<% end %> <% end %>
</div> </div>
<%= link_to "New student", new_institution_student_path(@institution) %> <div id="students-list">
<% @students.each do |student| %>
<div class="student-card">
<div class="student-info">
<h3><%= student.first_name %> <%= student.last_name %></h3>
<p>Place: <%= student.place %></p>
<p>
Institution:
<% if student.institution.present? %>
<%= link_to student.institution.name, institution_path(student.institution), class: "badge badge-primary" %>
<% else %>
<span class="badge badge-secondary">No institution</span>
<% end %>
</p>
</div>
<%# view path: prefer nested if current page is nested, else use student's institution if present %>
<% view_path = if @institution.present?
institution_student_path(@institution, student)
elsif student.institution.present?
institution_student_path(student.institution, student)
else
student_path(student)
end %>
<%= link_to "View →", view_path, class: "btn-small" %>
</div>
<% end %>
<% if @students.empty? %>
<p class="text-center" style="color:#6b7280;">No students found.</p>
<% end %>
</div>
</div>

View File

@ -29,3 +29,10 @@
en: en:
hello: "Hello world" hello: "Hello world"
number:
currency:
format:
unit: "₹"
separator: "."
delimiter: ","
format: "%u%n"

View File

@ -1,11 +1,37 @@
Rails.application.routes.draw do Rails.application.routes.draw do
resources :students
resources :incomes do
collection do
get 'report'
end
end
get 'incomes/report', to: 'incomes#report', as: :income_report
resources :expenses do
collection do
get 'report'
end
end
get 'expenses/report', to: 'expenses#report', as: :expense_report
resources :exclusive_traditional_records
resources :ziyaras resources :ziyaras
resources :programs resources :programs
resources :institutions do resources :institutions do
resources :students resources :students
end end
resources :institutions do
resources :students
end
get "up" => "rails/health#show", as: :rails_health_check get "up" => "rails/health#show", as: :rails_health_check
get 'contact', to: 'institutions#contact'
get 'institutions', to: 'institutions#index'
get 'programs', to: 'programs#index'
get 'ziyaras', to: 'ziyaras#index'
get 'students', to: 'students#index'
get 'expenses', to: 'expenses#index'
root "institutions#index" root "institutions#index"
end end

View File

@ -0,0 +1,11 @@
class CreateExclusiveTraditionalRecords < ActiveRecord::Migration[8.0]
def change
create_table :exclusive_traditional_records do |t|
t.string :name
t.string :author
t.text :description
t.timestamps
end
end
end

View File

@ -0,0 +1,57 @@
# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
def change
# Use Active Record's configured type for primary and foreign keys
primary_key_type, foreign_key_type = primary_and_foreign_key_types
create_table :active_storage_blobs, id: primary_key_type do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.string :service_name, null: false
t.bigint :byte_size, null: false
t.string :checksum
if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end
t.index [ :key ], unique: true
end
create_table :active_storage_attachments, id: primary_key_type do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
t.references :blob, null: false, type: foreign_key_type
if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
create_table :active_storage_variant_records, id: primary_key_type do |t|
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
t.string :variation_digest, null: false
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
private
def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[ primary_key_type, foreign_key_type ]
end
end

View File

@ -0,0 +1,10 @@
class CreateExpenses < ActiveRecord::Migration[8.0]
def change
create_table :expenses do |t|
t.decimal :amount
t.date :date
t.timestamps
end
end
end

View File

@ -0,0 +1,13 @@
class CreateIncomes < ActiveRecord::Migration[8.0]
def change
create_table :incomes do |t|
t.string :title
t.decimal :amount
t.date :date
t.string :category
t.text :description
t.timestamps
end
end
end

68
db/schema.rb generated
View File

@ -10,7 +10,60 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_10_18_034921) do ActiveRecord::Schema[8.0].define(version: 2025_10_22_133611) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.string "service_name", null: false
t.bigint "byte_size", null: false
t.string "checksum"
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", force: :cascade do |t|
t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "exclusive_traditional_records", force: :cascade do |t|
t.string "name"
t.string "author"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "expenses", force: :cascade do |t|
t.decimal "amount"
t.date "date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "incomes", force: :cascade do |t|
t.string "title"
t.decimal "amount"
t.date "date"
t.string "category"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "institutions", force: :cascade do |t| create_table "institutions", force: :cascade do |t|
t.string "name" t.string "name"
t.string "institution_type" t.string "institution_type"
@ -18,6 +71,17 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_18_034921) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
create_table "models", force: :cascade do |t|
t.string "Income"
t.string "title"
t.decimal "amount"
t.date "date"
t.string "category"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "programs", force: :cascade do |t| create_table "programs", force: :cascade do |t|
t.string "name" t.string "name"
t.date "date" t.date "date"
@ -45,5 +109,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_18_034921) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "students", "institutions" add_foreign_key "students", "institutions"
end end

View File

@ -0,0 +1,48 @@
require "test_helper"
class ExclusiveTraditionalRecordsControllerTest < ActionDispatch::IntegrationTest
setup do
@exclusive_traditional_record = exclusive_traditional_records(:one)
end
test "should get index" do
get exclusive_traditional_records_url
assert_response :success
end
test "should get new" do
get new_exclusive_traditional_record_url
assert_response :success
end
test "should create exclusive_traditional_record" do
assert_difference("ExclusiveTraditionalRecord.count") do
post exclusive_traditional_records_url, params: { exclusive_traditional_record: { author: @exclusive_traditional_record.author, description: @exclusive_traditional_record.description, name: @exclusive_traditional_record.name } }
end
assert_redirected_to exclusive_traditional_record_url(ExclusiveTraditionalRecord.last)
end
test "should show exclusive_traditional_record" do
get exclusive_traditional_record_url(@exclusive_traditional_record)
assert_response :success
end
test "should get edit" do
get edit_exclusive_traditional_record_url(@exclusive_traditional_record)
assert_response :success
end
test "should update exclusive_traditional_record" do
patch exclusive_traditional_record_url(@exclusive_traditional_record), params: { exclusive_traditional_record: { author: @exclusive_traditional_record.author, description: @exclusive_traditional_record.description, name: @exclusive_traditional_record.name } }
assert_redirected_to exclusive_traditional_record_url(@exclusive_traditional_record)
end
test "should destroy exclusive_traditional_record" do
assert_difference("ExclusiveTraditionalRecord.count", -1) do
delete exclusive_traditional_record_url(@exclusive_traditional_record)
end
assert_redirected_to exclusive_traditional_records_url
end
end

View File

@ -0,0 +1,48 @@
require "test_helper"
class ExpensesControllerTest < ActionDispatch::IntegrationTest
setup do
@expense = expenses(:one)
end
test "should get index" do
get expenses_url
assert_response :success
end
test "should get new" do
get new_expense_url
assert_response :success
end
test "should create expense" do
assert_difference("Expense.count") do
post expenses_url, params: { expense: { amount: @expense.amount, date: @expense.date } }
end
assert_redirected_to expense_url(Expense.last)
end
test "should show expense" do
get expense_url(@expense)
assert_response :success
end
test "should get edit" do
get edit_expense_url(@expense)
assert_response :success
end
test "should update expense" do
patch expense_url(@expense), params: { expense: { amount: @expense.amount, date: @expense.date } }
assert_redirected_to expense_url(@expense)
end
test "should destroy expense" do
assert_difference("Expense.count", -1) do
delete expense_url(@expense)
end
assert_redirected_to expenses_url
end
end

View File

@ -0,0 +1,48 @@
require "test_helper"
class IncomesControllerTest < ActionDispatch::IntegrationTest
setup do
@income = incomes(:one)
end
test "should get index" do
get incomes_url
assert_response :success
end
test "should get new" do
get new_income_url
assert_response :success
end
test "should create income" do
assert_difference("Income.count") do
post incomes_url, params: { income: { amount: @income.amount, category: @income.category, date: @income.date, description: @income.description, title: @income.title } }
end
assert_redirected_to income_url(Income.last)
end
test "should show income" do
get income_url(@income)
assert_response :success
end
test "should get edit" do
get edit_income_url(@income)
assert_response :success
end
test "should update income" do
patch income_url(@income), params: { income: { amount: @income.amount, category: @income.category, date: @income.date, description: @income.description, title: @income.title } }
assert_redirected_to income_url(@income)
end
test "should destroy income" do
assert_difference("Income.count", -1) do
delete income_url(@income)
end
assert_redirected_to incomes_url
end
end

View File

@ -0,0 +1,11 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
author: MyString
description: MyText
two:
name: MyString
author: MyString
description: MyText

9
test/fixtures/expenses.yml vendored Normal file
View File

@ -0,0 +1,9 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
amount: 9.99
date: 2025-10-22
two:
amount: 9.99
date: 2025-10-22

15
test/fixtures/incomes.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
title: MyString
amount: 9.99
date: 2025-10-22
category: MyString
description: MyText
two:
title: MyString
amount: 9.99
date: 2025-10-22
category: MyString
description: MyText

View File

@ -0,0 +1,7 @@
require "test_helper"
class ExclusiveTraditionalRecordTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require "test_helper"
class ExpenseTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,7 @@
require "test_helper"
class IncomeTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@ -0,0 +1,45 @@
require "application_system_test_case"
class ExclusiveTraditionalRecordsTest < ApplicationSystemTestCase
setup do
@exclusive_traditional_record = exclusive_traditional_records(:one)
end
test "visiting the index" do
visit exclusive_traditional_records_url
assert_selector "h1", text: "Exclusive traditional records"
end
test "should create exclusive traditional record" do
visit exclusive_traditional_records_url
click_on "New exclusive traditional record"
fill_in "Author", with: @exclusive_traditional_record.author
fill_in "Description", with: @exclusive_traditional_record.description
fill_in "Name", with: @exclusive_traditional_record.name
click_on "Create Exclusive traditional record"
assert_text "Exclusive traditional record was successfully created"
click_on "Back"
end
test "should update Exclusive traditional record" do
visit exclusive_traditional_record_url(@exclusive_traditional_record)
click_on "Edit this exclusive traditional record", match: :first
fill_in "Author", with: @exclusive_traditional_record.author
fill_in "Description", with: @exclusive_traditional_record.description
fill_in "Name", with: @exclusive_traditional_record.name
click_on "Update Exclusive traditional record"
assert_text "Exclusive traditional record was successfully updated"
click_on "Back"
end
test "should destroy Exclusive traditional record" do
visit exclusive_traditional_record_url(@exclusive_traditional_record)
click_on "Destroy this exclusive traditional record", match: :first
assert_text "Exclusive traditional record was successfully destroyed"
end
end

View File

@ -0,0 +1,43 @@
require "application_system_test_case"
class ExpensesTest < ApplicationSystemTestCase
setup do
@expense = expenses(:one)
end
test "visiting the index" do
visit expenses_url
assert_selector "h1", text: "Expenses"
end
test "should create expense" do
visit expenses_url
click_on "New expense"
fill_in "Amount", with: @expense.amount
fill_in "Date", with: @expense.date
click_on "Create Expense"
assert_text "Expense was successfully created"
click_on "Back"
end
test "should update Expense" do
visit expense_url(@expense)
click_on "Edit this expense", match: :first
fill_in "Amount", with: @expense.amount
fill_in "Date", with: @expense.date
click_on "Update Expense"
assert_text "Expense was successfully updated"
click_on "Back"
end
test "should destroy Expense" do
visit expense_url(@expense)
click_on "Destroy this expense", match: :first
assert_text "Expense was successfully destroyed"
end
end

View File

@ -0,0 +1,49 @@
require "application_system_test_case"
class IncomesTest < ApplicationSystemTestCase
setup do
@income = incomes(:one)
end
test "visiting the index" do
visit incomes_url
assert_selector "h1", text: "Incomes"
end
test "should create income" do
visit incomes_url
click_on "New income"
fill_in "Amount", with: @income.amount
fill_in "Category", with: @income.category
fill_in "Date", with: @income.date
fill_in "Description", with: @income.description
fill_in "Title", with: @income.title
click_on "Create Income"
assert_text "Income was successfully created"
click_on "Back"
end
test "should update Income" do
visit income_url(@income)
click_on "Edit this income", match: :first
fill_in "Amount", with: @income.amount
fill_in "Category", with: @income.category
fill_in "Date", with: @income.date
fill_in "Description", with: @income.description
fill_in "Title", with: @income.title
click_on "Update Income"
assert_text "Income was successfully updated"
click_on "Back"
end
test "should destroy Income" do
visit income_url(@income)
click_on "Destroy this income", match: :first
assert_text "Income was successfully destroyed"
end
end