Initial commit for course branch
This commit is contained in:
parent
4d1ede3531
commit
ea11dc7a9c
|
|
@ -3,11 +3,17 @@ class CategoriesController < ApplicationController
|
|||
|
||||
# GET /categories or /categories.json
|
||||
def index
|
||||
@categories = Category.all
|
||||
@categories = Category.includes(:products).all
|
||||
|
||||
@categories.each do |category|
|
||||
category.products = category.products || []
|
||||
end
|
||||
end
|
||||
|
||||
# GET /categories/1 or /categories/1.json
|
||||
def show
|
||||
# `set_category` before_action will load @category; expose associated products as @products
|
||||
@products = @category.products
|
||||
end
|
||||
|
||||
# GET /categories/new
|
||||
|
|
@ -60,7 +66,7 @@ class CategoriesController < ApplicationController
|
|||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_category
|
||||
@category = Category.find(params.expect(:id))
|
||||
@category = Category.find(params[:id])
|
||||
end
|
||||
|
||||
# Only allow a list of trusted parameters through.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ class ProductsController < ApplicationController
|
|||
|
||||
# GET /products/1 or /products/1.json
|
||||
def show
|
||||
@category = Category.find_by(id: params[:id])
|
||||
@product = Product.find_by(id: params[:id])
|
||||
end
|
||||
|
||||
# GET /products/new
|
||||
|
|
@ -65,6 +67,6 @@ class ProductsController < ApplicationController
|
|||
|
||||
# Only allow a list of trusted parameters through.
|
||||
def product_params
|
||||
params.expect(product: [ :name, :description, :price, images: [] ])
|
||||
params.require(:product).permit(:name, :category_id, :description, :price, images: [])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class Category < ApplicationRecord
|
||||
has_rich_text :description
|
||||
has_one_attached :image
|
||||
# Use lowercase association name so ActiveRecord sets up `category.products`
|
||||
has_many :products, dependent: :destroy
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class Product < ApplicationRecord
|
||||
has_rich_text :description
|
||||
has_many_attached :images
|
||||
|
||||
belongs_to :category
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
<div id="<%= dom_id category %>">
|
||||
<p>
|
||||
<strong>Name:</strong>
|
||||
<%= category.name %>
|
||||
</p>
|
||||
<h1><%= category.name %></h1>
|
||||
|
||||
<p>
|
||||
<strong>Description:</strong>
|
||||
|
|
@ -14,4 +11,14 @@
|
|||
<%= link_to category.image.filename, category.image if category.image.attached? %>
|
||||
</p>
|
||||
|
||||
<% if products.present? %>
|
||||
<h2>Products in this Category</h2>
|
||||
<ul>
|
||||
<% products.each do |product| %>
|
||||
<li><%= product.name %> - $<%= product.price %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p>No products available in this category.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<div id="categories">
|
||||
<% @categories.each do |category| %>
|
||||
<%= render category %>
|
||||
<%= render partial: "category", locals: {category: category, products: category.products} %>
|
||||
<p>
|
||||
<%= link_to "Show this category", category %>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<p style="color: green"><%= notice %></p>
|
||||
|
||||
<%= render @category %>
|
||||
<%= render partial: "category", locals: { category: @category, products: @products } %>
|
||||
|
||||
<div>
|
||||
<%= link_to "Edit this category", edit_category_path(@category) %> |
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ html, body {
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Poppins", sans-serif;
|
||||
background: linear-gradient(135deg, #0f1724, #1e293b);
|
||||
color: #e6eef6;
|
||||
background: #181a1b !important;
|
||||
color: #0f1724;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
|
|
@ -65,7 +65,7 @@ html, body {
|
|||
}
|
||||
|
||||
.container {
|
||||
max-width: 1900px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
padding-top: 20px;
|
||||
flex: 1;
|
||||
|
|
|
|||
|
|
@ -150,10 +150,50 @@ background: linear-gradient(-45deg, #1e88e5, #43a047, #6a1b9a, #d81b60);
|
|||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
width: 88%;
|
||||
margin-left: 12%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
/* Sidebar styles */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0; /* align under navbar */
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 240px;
|
||||
padding: 18px;
|
||||
background: rgba(28,30,33,0.95);
|
||||
color: #e6eef0;
|
||||
border-right: 1px solid rgba(255,255,255,0.03);
|
||||
z-index: 1040;
|
||||
transition: transform .28s ease, width .2s ease;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.sidebar .side-logo { width:44px; height:44px; border-radius:8px; object-fit:cover; }
|
||||
.sidebar .sidebar-brand { display:flex; align-items:center; gap:12px; margin-bottom:12px; }
|
||||
.sidebar .brand-text { font-weight:700; color:#bfe6c8; font-size:1rem }
|
||||
.sidebar .sidebar-toggle { margin-left:auto; background:transparent; border:none; color: #cfe9d8; font-size:1.1rem; cursor:pointer; }
|
||||
.sidebar-nav ul { list-style:none; padding:0; margin:6px 0; }
|
||||
.sidebar-nav li { margin:8px 0; }
|
||||
.sidebar-nav a { color: #e6eef0; text-decoration:none; display:flex; gap:10px; align-items:center; padding:8px 10px; border-radius:8px; }
|
||||
.sidebar-nav a:hover { background: rgba(30,136,229,0.12); color: #fff; }
|
||||
|
||||
/* Main content shift */
|
||||
.main-container { margin-left: 0; transition: margin-left .28s ease; }
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.main-container { margin-left: 260px; }
|
||||
}
|
||||
|
||||
/* Collapsed state */
|
||||
.sidebar.collapsed { transform: translateX(-100%); }
|
||||
@media (max-width: 991px) {
|
||||
.sidebar { transform: translateX(-100%); }
|
||||
.sidebar.open { transform: translateX(0); }
|
||||
.main-container { margin-left: 0; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<%# Includes all stylesheet files in app/assets/stylesheets %>
|
||||
|
||||
|
|
@ -178,27 +218,6 @@ background: linear-gradient(-45deg, #1e88e5, #43a047, #6a1b9a, #d81b60);
|
|||
<!-- Nav Links -->
|
||||
<div class="collapse navbar-collapse" id="mainNav">
|
||||
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<%= link_to "🏠 Home", root_path, class: "nav-link active" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "🎓 Programs", programs_path, class: "nav-link" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "🕌 Ziyara", ziyaras_path, class: "nav-link" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "📚 All Students", students_path, class: "nav-link" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "💰 Income", incomes_path, class: "nav-link" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "📊 Expenses", expenses_path, class: "nav-link" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "🏫 Records", exclusive_traditional_records_path, class: "nav-link" %>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<%= link_to "📞 Contact", contact_path, class: "nav-link" %>
|
||||
</li>
|
||||
|
|
@ -281,9 +300,29 @@ background: linear-gradient(-45deg, #1e88e5, #43a047, #6a1b9a, #d81b60);
|
|||
|
||||
<% content_for :title, "Institutions & Programs" %>
|
||||
|
||||
<!-- LEFT SIDEBAR -->
|
||||
<aside class="sidebar" id="appSidebar" aria-label="Main navigation">
|
||||
<div class="sidebar-brand">
|
||||
<%= image_tag("unnamed.jpg", alt: "Logo", class: "side-logo") %>
|
||||
<div class="brand-text">Badar Madeena</div>
|
||||
<button class="sidebar-toggle" id="sidebarToggle" aria-label="Toggle sidebar">☰</button>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<ul>
|
||||
<li><%= link_to "🏠 Home", root_path %></li>
|
||||
<li><%= link_to "🎓 Programs", programs_path %></li>
|
||||
<li><%= link_to "🕌 Ziyara", ziyaras_path %></li>
|
||||
<li><%= link_to "📚 Students", students_path %></li>
|
||||
<li><%= link_to "💰 Income", incomes_path %></li>
|
||||
<li><%= link_to "📊 Expenses", expenses_path %></li>
|
||||
<li><%= link_to "🏫 Records", exclusive_traditional_records_path %></li>
|
||||
<li><%= link_to "📞 Products", products_path %></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<% if flash.any? %>
|
||||
<% if flash.any? %>
|
||||
<% flash.each do |name, message| %>
|
||||
<% if name.to_s == 'notice' || name.to_s == 'alert' %>
|
||||
<div class="alert alert-<%= name.to_s == 'notice' ? 'success' : 'danger' %>">
|
||||
|
|
@ -293,13 +332,31 @@ background: linear-gradient(-45deg, #1e88e5, #43a047, #6a1b9a, #d81b60);
|
|||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div class="container shadow rounded p-4 bg-white mt-4">
|
||||
<%= yield %>
|
||||
|
||||
<div class="main-container">
|
||||
<div class="container shadow rounded p-4 bg-white mt-4">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer text-center mt-5 mb-3 text-muted">
|
||||
© <%= Time.current.year %> Badar Madeena
|
||||
</footer>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Sidebar toggle behavior
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggle = document.getElementById('sidebarToggle');
|
||||
const sidebar = document.getElementById('appSidebar');
|
||||
if (toggle && sidebar) {
|
||||
toggle.addEventListener('click', function () {
|
||||
// For small screens toggle open class
|
||||
if (window.innerWidth < 992) {
|
||||
sidebar.classList.toggle('open');
|
||||
} else {
|
||||
sidebar.classList.toggle('collapsed');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@
|
|||
<%= form.text_field :name %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.label :category_id, "Category", style: "display: block" %>
|
||||
<%= form.collection_select :category_id, Category.all, :id, :name, prompt: "Select Category" %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.label :description, style: "display: block" %>
|
||||
<%= form.rich_textarea :description %>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@
|
|||
<%= product.description %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Category:</strong>
|
||||
<%= product.category.name if product.category %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Images:</strong>
|
||||
<% product.images.each do |image| %>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ Rails.application.routes.draw do
|
|||
resources :students
|
||||
end
|
||||
|
||||
resources :categories do
|
||||
resources :products
|
||||
end
|
||||
|
||||
get "up" => "rails/health#show", as: :rails_health_check
|
||||
get 'contact', to: 'institutions#contact'
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
class AddCategoryToProducts < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_reference :products, :category, null: true, foreign_key: true
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_11_06_035813) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_11_06_132715) do
|
||||
create_table "action_mailbox_inbound_emails", force: :cascade do |t|
|
||||
t.integer "status", default: 0, null: false
|
||||
t.string "message_id", null: false
|
||||
|
|
@ -115,6 +115,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_11_06_035813) do
|
|||
t.decimal "price"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "category_id"
|
||||
t.index ["category_id"], name: "index_products_on_category_id"
|
||||
end
|
||||
|
||||
create_table "programs", force: :cascade do |t|
|
||||
|
|
@ -159,5 +161,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_11_06_035813) do
|
|||
|
||||
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 "products", "categories"
|
||||
add_foreign_key "students", "institutions"
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue