chore: init commit
in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
||||
<% if blob.representable? %>
|
||||
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [800, 600] : [1024, 768]) %>
|
||||
<% end %>
|
||||
|
||||
<figcaption class="attachment__caption">
|
||||
<% if caption = blob.try(:caption) %>
|
||||
<%= caption %>
|
||||
<% else %>
|
||||
<span class="attachment__name"><%= blob.filename %></span>
|
||||
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
|
||||
<% end %>
|
||||
</figcaption>
|
||||
</figure>
|
||||
@@ -0,0 +1,25 @@
|
||||
<%= form_with model: @announcement, url: @announcement.persisted? ? admin_announcement_path(@announcement) : admin_announcements_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Announcement Details</h3>
|
||||
|
||||
<%= f.text_field :title,
|
||||
label: "Title",
|
||||
hint: "Enter the announcement title (max 255 characters)",
|
||||
placeholder: "Important Update" %>
|
||||
|
||||
<%= f.rich_text_area :content,
|
||||
label: "Content",
|
||||
hint: "Write the full announcement content" %>
|
||||
|
||||
<%= f.boolean_field :featured,
|
||||
label: "Featured",
|
||||
hint: "Only one announcement can be featured at a time. Featuring this will unfeature the current one." %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @announcement.persisted? ? admin_announcement_path(@announcement) : admin_announcements_path %>
|
||||
<%= f.submit_button @announcement.persisted? ? "Update Announcement" : "Create Announcement" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit Announcement</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @announcement.title %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<%= admin_table(
|
||||
@announcements,
|
||||
columns: [
|
||||
{ attribute: :title, label: "Title", sortable: true },
|
||||
{ attribute: :featured, label: "Featured", sortable: true },
|
||||
{ attribute: :created_at, label: "Created", sortable: true },
|
||||
{ attribute: :updated_at, label: "Updated", sortable: true }
|
||||
],
|
||||
title: "Announcements",
|
||||
actions: link_to(
|
||||
"New Announcement",
|
||||
new_admin_announcement_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
),
|
||||
empty_message: "No announcements found. Create your first announcement to get started."
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New Announcement</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,42 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @announcement.title %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_announcement_path(@announcement),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_announcement_path(@announcement),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this announcement?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@announcement,
|
||||
attributes: %i[id title featured created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Rich Text Content Display -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Content</h3>
|
||||
<div class="prose max-w-none">
|
||||
<%= @announcement.content %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Announcements", admin_announcements_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
<%= form_with model: @classroom, url: @classroom.persisted? ? admin_classroom_path(@classroom) : admin_classrooms_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Classroom Details</h3>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Name",
|
||||
hint: "Enter the classroom name (e.g., Ms. Smith's 5th Grade)",
|
||||
placeholder: "Ms. Smith's 5th Grade" %>
|
||||
|
||||
<%= f.collection_check_boxes :grade_ids, Grade.all, :id, :name,
|
||||
label: "Grades",
|
||||
hint: "Select the grade levels for this classroom" do |b|
|
||||
content_tag :div, class: "flex items-center" do
|
||||
b.check_box(class: "mr-2") + b.label(class: "text-sm")
|
||||
end
|
||||
end %>
|
||||
|
||||
<%= f.select :school_year_id,
|
||||
options_from_collection_for_select(
|
||||
SchoolYear.joins(:school, :year).order("schools.name, years.name"),
|
||||
:id,
|
||||
:name,
|
||||
@classroom.school_year_id
|
||||
),
|
||||
label: "School Year",
|
||||
hint: "Select the school year for this classroom",
|
||||
include_blank: "Select a school year" %>
|
||||
|
||||
<%= f.boolean_field :trading_enabled,
|
||||
label: "Trading Enabled",
|
||||
hint: "Allow students to trade stocks in this classroom" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @classroom.persisted? ? admin_classroom_path(@classroom) : admin_classrooms_path %>
|
||||
<%= f.submit_button @classroom.persisted? ? "Update Classroom" : "Create Classroom" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit Classroom</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @classroom.name %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,106 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Classrooms</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New Classroom", new_admin_classroom_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:name, "Name") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Grade
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
School Year
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:archived, "Archived") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:trading_enabled, "Trading Enabled") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @classrooms.any? %>
|
||||
<% @classrooms.each do |classroom| %>
|
||||
<tr id="<%= dom_id(classroom) %>" class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= admin_classroom_path(classroom) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= classroom.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= classroom.name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= classroom.grades.pluck(:name).join(', ') %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= "#{classroom.school_name} - #{classroom.year_name}" %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<% if classroom.archived? %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<% if classroom.trading_enabled? %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_classroom_path(classroom), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_classroom_path(classroom), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<% if classroom.archived? %>
|
||||
<%= link_to "Activate", toggle_archive_admin_classroom_path(classroom),
|
||||
data: { turbo_method: :patch, turbo_confirm: "Are you sure you want to activate this classroom?" },
|
||||
class: "text-blue-600 hover:text-blue-800" %>
|
||||
<% else %>
|
||||
<%= link_to "Archive", toggle_archive_admin_classroom_path(classroom),
|
||||
data: { turbo_method: :patch, turbo_confirm: "Are you sure you want to archive this classroom? Teachers and students will no longer be able to access it, but all historical data will be preserved." },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="7" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No classrooms found. Create your first classroom to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New Classroom</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,76 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @classroom.name %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_classroom_path(@classroom),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= classroom_archive_toggle_button(@classroom) %>
|
||||
<%= link_to admin_classroom_path(@classroom),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this classroom?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@classroom,
|
||||
attributes: %i[id name grades_display archived trading_enabled school_year created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Students -->
|
||||
<% if @classroom.students.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Students (<%= @classroom.students.count %>)</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @classroom.students.limit(10).each do |student| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= student.username %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if @classroom.students.count > 10 %>
|
||||
<li class="text-sm text-gray-500">
|
||||
... and <%= @classroom.students.count - 10 %> more
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No students in this classroom yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Associated Teachers -->
|
||||
<% if @classroom.teachers.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Teachers</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @classroom.teachers.each do |teacher| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= teacher.username %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No teachers assigned to this classroom yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Classrooms", admin_classrooms_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,282 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Page Header -->
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mt-8 mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">Form Builder Demo</h1>
|
||||
<p class="text-gray-600">Showcase of Admin::FormBuilder with all field types</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Box -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-info-circle text-blue-600 mt-1 mr-3"></i>
|
||||
<div>
|
||||
<h3 class="font-semibold text-blue-900 mb-1">Form Builder Features</h3>
|
||||
<p class="text-sm text-blue-800">
|
||||
This form builder provides consistent Tailwind styling, automatic error display,
|
||||
and support for all common field types including associations, currency fields, and rich text.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Demo -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-6">Sample Form</h2>
|
||||
|
||||
<%= form_with model: @user, url: "#", builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<!-- Text Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Text Fields</h3>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Full Name",
|
||||
hint: "Enter the user's full name" %>
|
||||
|
||||
<%= f.email_field :email,
|
||||
label: "Email Address",
|
||||
placeholder: "user@example.com" %>
|
||||
|
||||
<%= f.password_field :password,
|
||||
label: "Password",
|
||||
hint: "Minimum 8 characters" %>
|
||||
</div>
|
||||
|
||||
<!-- Number & Currency Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Number & Currency Fields</h3>
|
||||
|
||||
<%= f.number_field :id,
|
||||
label: "User ID",
|
||||
hint: "Auto-generated unique identifier" %>
|
||||
|
||||
<%= f.currency_field :id,
|
||||
label: "Account Balance",
|
||||
multiplier: 0.01,
|
||||
decimals: 2,
|
||||
hint: "Currency field with $ prefix" %>
|
||||
</div>
|
||||
|
||||
<!-- Text Area -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Text Area</h3>
|
||||
|
||||
<%= f.text_area :email,
|
||||
label: "Biography",
|
||||
rows: 4,
|
||||
hint: "Tell us about yourself" %>
|
||||
</div>
|
||||
|
||||
<!-- Select Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Select Fields</h3>
|
||||
|
||||
<%= f.select_field :type,
|
||||
label: "User Type",
|
||||
collection: [%w[Student Student], %w[Teacher Teacher], %w[User User]],
|
||||
include_blank: "Select a type..." %>
|
||||
</div>
|
||||
|
||||
<!-- Boolean Field -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Boolean Field</h3>
|
||||
|
||||
<%= f.boolean_field :admin,
|
||||
label: "Administrator",
|
||||
hint: "Grant admin privileges to this user" %>
|
||||
</div>
|
||||
|
||||
<!-- Date & DateTime Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Date & DateTime Fields</h3>
|
||||
|
||||
<%= f.date_field :created_at,
|
||||
label: "Registration Date" %>
|
||||
|
||||
<%= f.datetime_field :updated_at,
|
||||
label: "Last Updated" %>
|
||||
</div>
|
||||
|
||||
<!-- Read-Only Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Read-Only Fields</h3>
|
||||
|
||||
<%= f.read_only_field :id,
|
||||
label: "User ID (Read-Only)" %>
|
||||
|
||||
<%= f.read_only_field :created_at,
|
||||
label: "Member Since" %>
|
||||
|
||||
<%= f.read_only_field :admin,
|
||||
label: "Administrator Status" %>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center justify-end gap-x-4 pt-4">
|
||||
<%= f.cancel_button "Cancel", url: admin_component_demo_index_path %>
|
||||
<%= f.submit_button "Save Changes" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validation Error Demo -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Validation Error Display</h2>
|
||||
<p class="text-gray-600 mb-4">
|
||||
The form builder automatically displays validation errors inline with fields.
|
||||
</p>
|
||||
|
||||
<%= link_to "Show Form with Errors",
|
||||
form_admin_component_demo_index_path(show_errors: true),
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Examples -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Usage Examples</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Basic Form Setup</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= form_with model: @resource, builder: Admin::FormBuilder do |f| %>
|
||||
<%= f.text_field :name, label: "Name", hint: "Enter the resource name" %>
|
||||
<%= f.submit_button "Save" %>
|
||||
<% end %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Currency Field</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.currency_field :amount_cents,
|
||||
multiplier: 0.01,
|
||||
decimals: 2,
|
||||
label: "Amount" %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Association Select</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.association_select :classroom_id,
|
||||
collection: Classroom.all,
|
||||
label_method: :name,
|
||||
value_method: :id,
|
||||
include_blank: "Select a classroom..." %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Boolean Field</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.boolean_field :active,
|
||||
label: "Active",
|
||||
hint: "Check to activate this resource" %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Read-Only Field</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.read_only_field :created_at,
|
||||
label: "Created At",
|
||||
value: @resource.created_at.strftime("%B %d, %Y") %></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Field Reference -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Available Field Types</h2>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">Method</th>
|
||||
<th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Description</th>
|
||||
<th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">text_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Standard text input</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, placeholder</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">email_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Email input with validation</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, placeholder</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">password_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Password input (masked)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">number_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Numeric input</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, step</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">currency_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Currency with $ prefix</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">multiplier, decimals, label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">text_area</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Multi-line text input</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, rows</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">rich_text_area</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Rich text editor (Trix)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">select_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Dropdown select</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">collection, include_blank, label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">boolean_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Checkbox with label</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">date_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Date picker</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">datetime_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Date and time picker</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">association_select</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Association dropdown</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">collection, label_method, value_method, include_blank</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">read_only_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Display-only field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, value, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">submit_button</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Submit button (blue)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">cancel_button</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Cancel link (styled as button)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">text, url</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,193 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Page Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">Admin Components Demo</h1>
|
||||
<p class="text-gray-600">Showcase of all reusable admin components</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Box -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-info-circle text-blue-600 mt-1 mr-3"></i>
|
||||
<div>
|
||||
<h3 class="font-semibold text-blue-900 mb-1">Component Demo Page</h3>
|
||||
<p class="text-sm text-blue-800">
|
||||
This page demonstrates all the reusable components created for the Admin V2 interface.
|
||||
These components will be used across all admin resource pages.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg p-4 mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Component Demos</h3>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to form_admin_component_demo_index_path, class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" do %>
|
||||
<i class="fas fa-edit mr-2"></i>
|
||||
Form Builder Demo
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumbs Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Breadcrumbs Component</h2>
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Search/Filter Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Search & Filter Component</h2>
|
||||
<%= admin_search_filter(
|
||||
filters: @filters,
|
||||
search_placeholder: "Search users..."
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Table Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Table Component</h2>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Responsive table with sortable columns and empty states.
|
||||
Click column headers to sort. (Action buttons disabled in demo - see Action Buttons section below)
|
||||
</p>
|
||||
|
||||
<%= admin_table(
|
||||
@users,
|
||||
columns: @columns,
|
||||
title: "Users",
|
||||
actions: link_to("New User", "#", class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"),
|
||||
empty_message: "No users found. Create your first user to get started.",
|
||||
show_actions: false
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Show Attributes Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Show Attributes Component</h2>
|
||||
<p class="text-gray-600 mb-4">Clean attribute display for show/detail pages.</p>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@sample_user,
|
||||
attributes: %i[id email name type admin created_at updated_at]
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Action Buttons Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Action Buttons Component</h2>
|
||||
<p class="text-gray-600 mb-4">Standard and custom action buttons with icons.</p>
|
||||
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm ring-1 ring-gray-900/5">
|
||||
<h3 class="text-sm font-medium text-gray-900 mb-4">Standard Actions (Edit + Delete)</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<%= link_to "#", class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to "#", data: { turbo_method: :delete, turbo_confirm: "Are you sure?" }, class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<hr class="my-6">
|
||||
|
||||
<h3 class="text-sm font-medium text-gray-900 mb-4">With Custom Actions</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<%= link_to "#", class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to "#", class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-blue-700 bg-blue-50 hover:bg-blue-100 border-blue-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-envelope -ml-1 mr-2 h-5 w-5"></i>
|
||||
Send Email
|
||||
<% end %>
|
||||
<%= link_to "#", data: { turbo_confirm: "Archive this user?" }, class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-archive -ml-1 mr-2 h-5 w-5"></i>
|
||||
Archive
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Boolean Badge Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Boolean Badges</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm ring-1 ring-gray-900/5">
|
||||
<div class="flex gap-4 items-center">
|
||||
<div>
|
||||
<span class="text-sm text-gray-600 mr-2">True:</span>
|
||||
<%= boolean_badge(true) %>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-600 mr-2">False:</span>
|
||||
<%= boolean_badge(false) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Format Attribute Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Attribute Formatting Examples</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm ring-1 ring-gray-900/5">
|
||||
<dl class="space-y-4">
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">String:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(@sample_user, :email) %></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">Boolean:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(@sample_user, :admin) %></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">Date:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(@sample_user, :created_at) %></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">Nil Value:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(User.new, :name) %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Component Usage Examples -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Usage Examples</h2>
|
||||
<div class="bg-gray-50 p-6 rounded-lg">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Table Component</h3>
|
||||
<pre class="text-xs bg-white p-4 rounded border border-gray-200 overflow-x-auto"><code><%= admin_table(
|
||||
@resources,
|
||||
columns: [
|
||||
{ attribute: :name, label: "Name", sortable: true, link: true },
|
||||
{ attribute: :status, label: "Status", sortable: true }
|
||||
],
|
||||
title: "Resources",
|
||||
actions: link_to("New", [:new, :admin, :resource])
|
||||
) %></code></pre>
|
||||
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3 mt-6">Show Attributes</h3>
|
||||
<pre class="text-xs bg-white p-4 rounded border border-gray-200 overflow-x-auto"><code><%= admin_show_attributes(
|
||||
@resource,
|
||||
attributes: [:name, :email, :created_at]
|
||||
) %></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">User Details</h1>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@user,
|
||||
attributes: %i[id email name type admin classroom_id created_at updated_at]
|
||||
) %>
|
||||
|
||||
<div class="mt-6">
|
||||
<%= admin_actions(
|
||||
@user,
|
||||
actions: %i[edit delete],
|
||||
custom_actions: [
|
||||
{
|
||||
label: "Back to List",
|
||||
path: admin_component_demo_index_path,
|
||||
icon: "fas fa-arrow-left",
|
||||
class: "text-gray-700 bg-white hover:bg-gray-50 border-gray-300"
|
||||
}
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,102 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Admin Dashboard</h1>
|
||||
<p class="text-gray-600 mt-2">Welcome to the in-house admin interface</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<!-- Academic Resources -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-graduation-cap text-blue-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Academic</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_classrooms_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-chalkboard w-4 mr-2"></i>Classrooms
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_schools_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-school w-4 mr-2"></i>Schools
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_school_years_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-calendar-check w-4 mr-2"></i>School Years
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- User Management -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-users text-green-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">User Management</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_students_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-user-graduate w-4 mr-2"></i>Students
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_teachers_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-chalkboard-teacher w-4 mr-2"></i>Teachers
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_users_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-users w-4 mr-2"></i>Users
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Portfolio & Finance -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-chart-line text-purple-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Portfolio & Finance</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_stocks_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-chart-line w-4 mr-2"></i>Stocks
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="text-gray-600"><i class="fas fa-exchange-alt w-4 mr-2"></i>Portfolio Transactions</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-bullhorn text-orange-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Content</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_announcements_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-bullhorn w-4 mr-2"></i>Announcements
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-600 mt-1 mr-3"></i>
|
||||
<div>
|
||||
<h3 class="font-semibold text-yellow-900 mb-1">Development Status</h3>
|
||||
<p class="text-sm text-yellow-800">
|
||||
Resource pages are being built incrementally. Use the sidebar navigation to access completed resources.
|
||||
Links marked with "#" are not yet implemented.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
<%= form_with model: portfolio_transaction, url: portfolio_transaction.persisted? ? admin_portfolio_transaction_path(portfolio_transaction) : admin_portfolio_transactions_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Transaction Details</h3>
|
||||
|
||||
<%= f.association_select :portfolio_id,
|
||||
collection: Portfolio.includes(:user).order("users.username"),
|
||||
label: "Portfolio",
|
||||
label_method: ->(portfolio) { "#{portfolio.user.username} (ID: #{portfolio.id})" },
|
||||
hint: "Select the portfolio for this transaction" %>
|
||||
|
||||
<%= f.select :transaction_type,
|
||||
PortfolioTransaction.transaction_types.keys.map { |type| [type.humanize, type] },
|
||||
label: "Transaction Type",
|
||||
hint: "Type of transaction (deposit, withdrawal, credit, debit, fee)",
|
||||
include_blank: "Select transaction type..." %>
|
||||
|
||||
<%= f.select :reason,
|
||||
PortfolioTransaction.reasons.keys.map { |r| [r.humanize, r] },
|
||||
label: "Reason",
|
||||
hint: "Reason for this transaction (optional)",
|
||||
include_blank: "Select reason..." %>
|
||||
|
||||
<%= f.currency_field :amount_cents,
|
||||
label: "Amount",
|
||||
hint: "Transaction amount in dollars",
|
||||
multiplier: 0.01,
|
||||
decimals: 2 %>
|
||||
|
||||
<%= f.text_area :description,
|
||||
label: "Description",
|
||||
hint: "Optional description for this transaction",
|
||||
rows: 4 %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: portfolio_transaction.persisted? ? admin_portfolio_transaction_path(portfolio_transaction) : admin_root_path %>
|
||||
<%= f.submit_button portfolio_transaction.persisted? ? "Update Portfolio Transaction" : "Create Portfolio Transaction" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">Edit Portfolio Transaction #<%= @portfolio_transaction.id %></h2>
|
||||
|
||||
<%= render "form", portfolio_transaction: @portfolio_transaction %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">New Portfolio Transaction</h2>
|
||||
|
||||
<%= render "form", portfolio_transaction: @portfolio_transaction %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900">Portfolio Transaction #<%= @portfolio_transaction.id %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_portfolio_transaction_path(@portfolio_transaction),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_portfolio_transaction_path(@portfolio_transaction),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this portfolio transaction?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@portfolio_transaction,
|
||||
attributes: %i[id created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Portfolio Information -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio</h3>
|
||||
<dl class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Portfolio ID</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<%= @portfolio_transaction.portfolio_id %>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">User</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @portfolio_transaction.portfolio.user.username %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Transaction Details -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Transaction Details</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Transaction Type</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<%= @portfolio_transaction.transaction_type.humanize %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Amount</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 font-semibold"><%= number_to_currency(@portfolio_transaction.amount_cents / 100.0) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Reason</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @portfolio_transaction.reason&.humanize || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<% if @portfolio_transaction.description.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Description</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @portfolio_transaction.description %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Associated Order -->
|
||||
<% if @portfolio_transaction.order.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Associated Order</h3>
|
||||
<p class="text-sm text-gray-900">Order #<%= @portfolio_transaction.order.id %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back", :back,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,45 @@
|
||||
<%= form_with model: @school_year, url: @school_year.persisted? ? admin_school_year_path(@school_year) : admin_school_years_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">School Year Details</h3>
|
||||
|
||||
<%= f.base_errors %>
|
||||
|
||||
<%= f.select :school_id,
|
||||
options_from_collection_for_select(
|
||||
School.order(:name),
|
||||
:id,
|
||||
:name,
|
||||
@school_year.school_id
|
||||
),
|
||||
label: "School",
|
||||
hint: "Select the school for this school year",
|
||||
include_blank: "Select a school" %>
|
||||
|
||||
<%= f.select :year_id,
|
||||
options_from_collection_for_select(
|
||||
Year.ordered_by_start_year,
|
||||
:id,
|
||||
:name,
|
||||
@school_year.year_id
|
||||
),
|
||||
label: "Year",
|
||||
hint: "Select the year for this school year",
|
||||
include_blank: "Select a year" %>
|
||||
|
||||
<% if @school_year.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-600">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Four quarters will be automatically created for this school year.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @school_year.persisted? ? admin_school_year_path(@school_year) : admin_school_years_path %>
|
||||
<%= f.submit_button @school_year.persisted? ? "Update School Year" : "Create School Year" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit School Year</h1>
|
||||
|
||||
<%= render "form", school_year: @school_year %>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900">School Years</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New School Year", new_admin_school_year_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
School
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Year
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:created_at, "Created At") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @school_years.any? %>
|
||||
<% @school_years.each do |school_year| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= admin_school_year_path(school_year) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.school_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.year_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.created_at.strftime("%B %d, %Y") %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_school_year_path(school_year), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_school_year_path(school_year), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<%= link_to "Delete", admin_school_year_path(school_year),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="5" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No school years found. Create your first school year to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New School Year</h1>
|
||||
|
||||
<%= render "form", school_year: @school_year %>
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= "#{@school_year.school_name} (#{@school_year.year_name})" %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_school_year_path(@school_year),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_school_year_path(@school_year),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this school year?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@school_year,
|
||||
attributes: %i[id school_name year_name created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Classrooms -->
|
||||
<% if @school_year.classrooms.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Classrooms (<%= @school_year.classrooms.count %>)</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @school_year.classrooms.limit(10).each do |classroom| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= classroom.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if @school_year.classrooms.count > 10 %>
|
||||
<li class="text-sm text-gray-500">
|
||||
... and <%= @school_year.classrooms.count - 10 %> more
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No classrooms associated with this school year yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Associated Quarters -->
|
||||
<% if @school_year.quarters.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Quarters</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @school_year.quarters.order(:number).each do |quarter| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= quarter.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to School Years", admin_school_years_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<%= form_with model: @school, url: @school.persisted? ? admin_school_path(@school) : admin_schools_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">School Details</h3>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Name",
|
||||
hint: "Enter the school name",
|
||||
placeholder: "Lincoln Elementary School" %>
|
||||
|
||||
<%= f.collection_check_boxes :year_ids, @years, :id, :name,
|
||||
label: "Years",
|
||||
hint: "Select the school years this school will be active" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @school.persisted? ? admin_school_path(@school) : admin_schools_path %>
|
||||
<%= f.submit_button @school.persisted? ? "Update School" : "Create School" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit School</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @school.name %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<%= admin_table(
|
||||
@schools,
|
||||
columns: [
|
||||
{ attribute: :id, label: "ID", sortable: true },
|
||||
{ attribute: :name, label: "Name", sortable: true },
|
||||
{ attribute: :created_at, label: "Created", sortable: true }
|
||||
],
|
||||
title: "Schools",
|
||||
actions: link_to(
|
||||
"New School",
|
||||
new_admin_school_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
),
|
||||
empty_message: "No schools found. Create your first school to get started."
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New School</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @school.name %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_school_path(@school),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_school_path(@school),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this school?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@school,
|
||||
attributes: %i[id name created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Years -->
|
||||
<% if @school.years.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Associated Years</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @school.years.each do |year| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= year.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No years associated with this school yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Schools", admin_schools_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<% if actions.include?(:edit) %>
|
||||
<%= link_to [:edit, :admin, resource], class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if actions.include?(:delete) %>
|
||||
<%= link_to [:admin, resource],
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this #{resource.class.name.downcase}?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% custom_actions.each do |action| %>
|
||||
<%= link_to action[:path],
|
||||
method: action[:method] || :get,
|
||||
data: action[:data] || {},
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md #{action[:class] || 'text-gray-700 bg-white hover:bg-gray-50'} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<% if action[:icon] %>
|
||||
<i class="<%= action[:icon] %> -ml-1 mr-2 h-5 w-5"></i>
|
||||
<% end %>
|
||||
<%= action[:label] %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
||||
<li class="inline-flex items-center">
|
||||
<%= link_to admin_root_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
||||
<i class="fas fa-home w-4 h-4 mr-2"></i>
|
||||
Dashboard
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<% breadcrumbs.each_with_index do |crumb, index| %>
|
||||
<li>
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-chevron-right w-3 h-3 text-gray-400 mx-1"></i>
|
||||
<% if index == breadcrumbs.length - 1 %>
|
||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">
|
||||
<%= crumb[:label] %>
|
||||
</span>
|
||||
<% else %>
|
||||
<%= link_to crumb[:label], crumb[:path], class: "ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</nav>
|
||||
@@ -0,0 +1,92 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Dashboard -->
|
||||
<div>
|
||||
<%= link_to root_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-home w-5 mr-3 text-gray-500"></i>
|
||||
Dashboard
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Academic Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Academic
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_classrooms_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-chalkboard w-5 mr-3 text-gray-500"></i>
|
||||
Classrooms
|
||||
<% end %>
|
||||
<%= link_to admin_schools_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-school w-5 mr-3 text-gray-500"></i>
|
||||
Schools
|
||||
<% end %>
|
||||
<%= link_to admin_school_years_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-calendar-check w-5 mr-3 text-gray-500"></i>
|
||||
School Years
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Users
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_students_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-user-graduate w-5 mr-3 text-gray-500"></i>
|
||||
Students
|
||||
<% end %>
|
||||
<%= link_to admin_teachers_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-chalkboard-teacher w-5 mr-3 text-gray-500"></i>
|
||||
Teachers
|
||||
<% end %>
|
||||
<%= link_to admin_users_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-users w-5 mr-3 text-gray-500"></i>
|
||||
Users
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Portfolio Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Portfolio
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_stocks_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-chart-line w-5 mr-3 text-gray-500"></i>
|
||||
Stocks
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Content
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_announcements_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-bullhorn w-5 mr-3 text-gray-500"></i>
|
||||
Announcements
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Development Section (remove in production) -->
|
||||
<% if Rails.env.development? %>
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Development
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_component_demo_index_path, class: "flex items-center px-3 py-2 text-sm font-medium text-purple-700 hover:bg-purple-50 hover:text-purple-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-flask w-5 mr-3 text-purple-500"></i>
|
||||
Component Demo
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
<% if collection.respond_to?(:total_pages) && collection.total_pages > 1 %>
|
||||
<nav class="flex items-center justify-between" aria-label="Pagination">
|
||||
<div class="hidden sm:block">
|
||||
<p class="text-sm text-gray-700">
|
||||
Showing
|
||||
<span class="font-medium"><%= collection.offset_value + 1 %></span>
|
||||
to
|
||||
<span class="font-medium"><%= [collection.offset_value + collection.limit_value, collection.total_count].min %></span>
|
||||
of
|
||||
<span class="font-medium"><%= collection.total_count %></span>
|
||||
results
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-1 justify-between sm:justify-end">
|
||||
<% if collection.prev_page %>
|
||||
<%= link_to "Previous", request.params.merge(page: collection.prev_page), class: "relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<% else %>
|
||||
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-400 bg-gray-100 cursor-not-allowed">
|
||||
Previous
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% if collection.next_page %>
|
||||
<%= link_to "Next", request.params.merge(page: collection.next_page), class: "ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<% else %>
|
||||
<span class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-400 bg-gray-100 cursor-not-allowed">
|
||||
Next
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</nav>
|
||||
<% end %>
|
||||
@@ -0,0 +1,38 @@
|
||||
<% filters ||= [] %>
|
||||
<% search_placeholder ||= "Search..." %>
|
||||
<div class="bg-white p-4 shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<%= form_with url: request.path, method: :get, class: "space-y-4 sm:space-y-0 sm:flex sm:items-center sm:gap-4" do |form| %>
|
||||
<!-- Search Field -->
|
||||
<div class="flex-1">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<div class="relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-5">
|
||||
<i class="fas fa-search h-4 w-4 text-gray-400"></i>
|
||||
</div>
|
||||
<%= form.text_field :search,
|
||||
value: params[:search],
|
||||
placeholder: search_placeholder,
|
||||
class: "block w-full rounded-md border-0 py-2 pl-14 pr-3 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Dropdowns -->
|
||||
<% filters.each do |filter| %>
|
||||
<div class="sm:w-48">
|
||||
<label for="<%= filter[:name] %>" class="sr-only"><%= filter[:label] %></label>
|
||||
<%= form.select filter[:name],
|
||||
options_for_select(filter[:options], params[filter[:name]]),
|
||||
{ include_blank: filter[:label] },
|
||||
class: "block w-full rounded-md border-0 py-2 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex gap-2">
|
||||
<%= form.submit "Filter", class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
||||
<% if params[:search].present? || filters.any? { |f| params[f[:name]].present? } %>
|
||||
<%= link_to "Clear", request.path, class: "inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<dl class="divide-y divide-gray-200">
|
||||
<% attributes.each do |attribute| %>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4" data-testid="<%= attribute %>">
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= attribute.to_s.humanize %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<%= format_attribute(resource, attribute) %>
|
||||
</dd>
|
||||
</div>
|
||||
<% end %>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,86 @@
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<% if local_assigns[:title] || local_assigns[:actions] %>
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<% if local_assigns[:title] %>
|
||||
<h3 class="text-lg font-semibold text-gray-900"><%= title %></h3>
|
||||
<% end %>
|
||||
<% if local_assigns[:actions] %>
|
||||
<div class="flex gap-2">
|
||||
<%= actions %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<% columns.each do |column| %>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<% if column[:sortable] %>
|
||||
<%= sort_link(column[:attribute], column[:label]) %>
|
||||
<% else %>
|
||||
<%= column[:label] %>
|
||||
<% end %>
|
||||
</th>
|
||||
<% end %>
|
||||
<% if options[:show_actions] != false %>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if collection.any? %>
|
||||
<% collection.each do |record| %>
|
||||
<tr id="<%= dom_id(record) %>" class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= url_for([:admin, route_model(record)]) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<% columns.each do |column| %>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900" data-testid="<%= column[:attribute] %>">
|
||||
<% if column[:link] %>
|
||||
<%= link_to format_attribute(record, column[:attribute]), [:admin, route_model(record)], class: "text-blue-600 hover:text-blue-800 font-medium" %>
|
||||
<% else %>
|
||||
<%= format_attribute(record, column[:attribute]) %>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
<% if options[:show_actions] != false %>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", [:admin, route_model(record)], class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", [:edit, :admin, route_model(record)], class: "text-gray-600 hover:text-gray-800" %>
|
||||
<%= link_to "Delete", [:admin, route_model(record)],
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
</div>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="<%= columns.length + (options[:show_actions] == false ? 0 : 1) %>" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<% if options[:empty_message] %>
|
||||
<p class="text-gray-400 mt-1"><%= options[:empty_message] %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<% if collection.respond_to?(:total_pages) && collection.total_pages > 1 %>
|
||||
<div class="px-4 py-3 border-t border-gray-200 sm:px-6">
|
||||
<%= render "admin/shared/pagination", collection: collection %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,126 @@
|
||||
<%= form_with model: @stock, url: @stock.persisted? ? admin_stock_path(@stock) : admin_stocks_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Basic Information</h3>
|
||||
|
||||
<%= f.text_field :ticker,
|
||||
label: "Ticker Symbol",
|
||||
hint: "Stock ticker symbol (e.g., AAPL, GOOGL)",
|
||||
placeholder: "AAPL" %>
|
||||
|
||||
<%= f.text_field :company_name,
|
||||
label: "Company Name",
|
||||
hint: "Full company name",
|
||||
placeholder: "Apple Inc." %>
|
||||
|
||||
<%= f.text_field :company_website,
|
||||
label: "Company Website",
|
||||
hint: "Company website URL (must be HTTP or HTTPS)",
|
||||
placeholder: "https://www.apple.com" %>
|
||||
|
||||
<%= f.text_field :stock_exchange,
|
||||
label: "Stock Exchange",
|
||||
hint: "Exchange where stock is traded (e.g., NASDAQ, NYSE)",
|
||||
placeholder: "NASDAQ" %>
|
||||
|
||||
<%= f.text_area :description,
|
||||
label: "Description",
|
||||
hint: "Company description and overview",
|
||||
rows: 4 %>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Pricing</h4>
|
||||
|
||||
<%= f.currency_field :price_cents,
|
||||
label: "Current Price",
|
||||
hint: "Current stock price in dollars",
|
||||
multiplier: 0.01,
|
||||
decimals: 2 %>
|
||||
|
||||
<%= f.currency_field :yesterday_price_cents,
|
||||
label: "Yesterday's Price",
|
||||
hint: "Previous day's closing price in dollars",
|
||||
multiplier: 0.01,
|
||||
decimals: 2 %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Company Details</h4>
|
||||
|
||||
<%= f.number_field :employees,
|
||||
label: "Number of Employees",
|
||||
hint: "Total number of employees" %>
|
||||
|
||||
<%= f.text_field :industry,
|
||||
label: "Industry",
|
||||
hint: "Industry sector",
|
||||
placeholder: "Technology" %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Financial Metrics</h4>
|
||||
|
||||
<%= f.text_field :cash_flow,
|
||||
label: "Cash Flow",
|
||||
hint: "Company cash flow" %>
|
||||
|
||||
<%= f.text_field :debt,
|
||||
label: "Debt",
|
||||
hint: "Total company debt" %>
|
||||
|
||||
<%= f.text_field :debt_to_equity,
|
||||
label: "Debt to Equity Ratio",
|
||||
hint: "Debt to equity ratio" %>
|
||||
|
||||
<%= f.text_field :profit_margin,
|
||||
label: "Profit Margin",
|
||||
hint: "Company profit margin" %>
|
||||
|
||||
<%= f.text_field :sales_growth,
|
||||
label: "Sales Growth",
|
||||
hint: "Sales growth percentage" %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Industry Averages</h4>
|
||||
|
||||
<%= f.text_field :industry_avg_debt_to_equity,
|
||||
label: "Industry Avg Debt to Equity",
|
||||
hint: "Industry average debt to equity ratio" %>
|
||||
|
||||
<%= f.text_field :industry_avg_profit_margin,
|
||||
label: "Industry Avg Profit Margin",
|
||||
hint: "Industry average profit margin" %>
|
||||
|
||||
<%= f.text_field :industry_avg_sales_growth,
|
||||
label: "Industry Avg Sales Growth",
|
||||
hint: "Industry average sales growth" %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Additional Information</h4>
|
||||
|
||||
<%= f.text_area :management,
|
||||
label: "Management",
|
||||
hint: "Information about company management",
|
||||
rows: 4 %>
|
||||
|
||||
<%= f.text_area :competitor_names,
|
||||
label: "Competitor Names",
|
||||
hint: "List of competitor companies",
|
||||
rows: 3 %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<%= f.boolean_field :archived,
|
||||
label: "Archived",
|
||||
hint: "Mark this stock as archived (hidden from active listings)" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @stock.persisted? ? admin_stock_path(@stock) : admin_stocks_path %>
|
||||
<%= f.submit_button @stock.persisted? ? "Update Stock" : "Create Stock" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit Stock</h1>
|
||||
|
||||
<%= render "form", stock: @stock %>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<%= admin_table(
|
||||
@stocks,
|
||||
columns: [
|
||||
{ attribute: :id, label: "ID", sortable: true },
|
||||
{ attribute: :ticker, label: "Ticker", sortable: true },
|
||||
{ attribute: :company_name, label: "Company Name", sortable: true },
|
||||
{ attribute: :company_website, label: "Website", sortable: false },
|
||||
{ attribute: :current_price, label: "Current Price", sortable: false },
|
||||
{ attribute: :archived, label: "Archived", sortable: true }
|
||||
],
|
||||
title: "Stocks",
|
||||
actions: link_to(
|
||||
"New Stock",
|
||||
new_admin_stock_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
),
|
||||
empty_message: "No stocks found. Create your first stock to get started."
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New Stock</h1>
|
||||
|
||||
<%= render "form", stock: @stock %>
|
||||
</div>
|
||||
@@ -0,0 +1,150 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @stock.ticker %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_stock_path(@stock),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_stock_path(@stock),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this stock?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@stock,
|
||||
attributes: %i[id ticker company_name company_website stock_exchange archived created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Description -->
|
||||
<% if @stock.description.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Description</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @stock.description %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Price Information -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Price Information</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Current Price</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= number_to_currency(@stock.current_price) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Yesterday's Price</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= number_to_currency(@stock.yesterday_price) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Percentage Change</dt>
|
||||
<dd class="mt-1 text-sm">
|
||||
<span class="<%= @stock.percentage_change.positive? ? 'text-green-600' : 'text-red-600' %>">
|
||||
<%= @stock.percentage_change_formatted %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Company Information -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Company Information</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Employees</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.employees || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Industry</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Financial Metrics -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Financial Metrics</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Cash Flow</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.cash_flow || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Debt</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.debt || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Debt to Equity</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.debt_to_equity || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Profit Margin</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.profit_margin || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Sales Growth</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.sales_growth || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Industry Averages -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Industry Averages</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Avg Debt to Equity</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry_avg_debt_to_equity || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Avg Profit Margin</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry_avg_profit_margin || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Avg Sales Growth</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry_avg_sales_growth || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Management -->
|
||||
<% if @stock.management.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Management</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @stock.management %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Competitors -->
|
||||
<% if @stock.competitor_names.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Competitor Names</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @stock.competitor_names %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Portfolio Stocks -->
|
||||
<% if @stock.portfolio_stocks.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio Stocks (<%= @stock.portfolio_stocks.count %>)</h3>
|
||||
<p class="text-sm text-gray-500">This stock is held in <%= @stock.portfolio_stocks.count %> portfolio(s)</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Stocks", admin_stocks_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,91 @@
|
||||
<%= form_with model: @student, url: @student.persisted? ? admin_student_path(@student) : admin_students_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Student Details</h3>
|
||||
|
||||
<%= f.text_field :username,
|
||||
label: "Username",
|
||||
hint: "Unique username for the student",
|
||||
placeholder: "johndoe" %>
|
||||
|
||||
<%= f.select :classroom_id,
|
||||
options_from_collection_for_select(
|
||||
Classroom.order(:name),
|
||||
:id,
|
||||
:name,
|
||||
@student.classroom_id
|
||||
),
|
||||
label: "Classroom",
|
||||
hint: "Classroom assignment (required)",
|
||||
include_blank: "Select a classroom" %>
|
||||
|
||||
<% if @student.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Password</h4>
|
||||
<p class="text-sm text-gray-500 mb-4">Leave blank to generate a random password automatically</p>
|
||||
|
||||
<%= f.password_field :password,
|
||||
label: "Password (Optional)",
|
||||
hint: "Minimum 6 characters, or leave blank to auto-generate",
|
||||
placeholder: "••••••••" %>
|
||||
|
||||
<%= f.password_field :password_confirmation,
|
||||
label: "Password Confirmation",
|
||||
hint: "Re-enter password to confirm",
|
||||
placeholder: "••••••••" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @student.persisted? ? admin_student_path(@student) : admin_students_path %>
|
||||
<%= f.submit_button @student.persisted? ? "Update Student" : "Create Student" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @student.persisted? %>
|
||||
<%= form_with url: add_transaction_admin_student_path(@student), method: :post, builder: Admin::FormBuilder, class: "space-y-6 mt-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Add Portfolio Transaction</h3>
|
||||
|
||||
<!-- Current Cash Balance (Read-only) -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Current Cash Balance</label>
|
||||
<div class="text-lg font-semibold text-gray-900">
|
||||
<%= number_to_currency(@student.portfolio&.cash_balance || 0) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= f.select :transaction_type,
|
||||
[["", ""], ["Deposit", "deposit"], ["Debit", "debit"]],
|
||||
label: "Transaction Type",
|
||||
hint: "Select deposit to add funds or debit to remove funds",
|
||||
include_blank: false %>
|
||||
|
||||
<%= f.number_field :add_fund_amount,
|
||||
label: "Amount",
|
||||
hint: "Transaction amount in dollars",
|
||||
placeholder: "0.00",
|
||||
step: "0.01" %>
|
||||
|
||||
<%= f.select :transaction_reason,
|
||||
PortfolioTransaction.reasons.keys.map { |key| [PortfolioTransaction.human_attribute_name("reason.#{key}"), key] },
|
||||
label: "Reason",
|
||||
hint: "Reason for this transaction",
|
||||
include_blank: "Select a reason" %>
|
||||
|
||||
<%= f.text_area :transaction_description,
|
||||
label: "Description (Optional)",
|
||||
hint: "Additional details about this transaction",
|
||||
placeholder: "Enter transaction details...",
|
||||
rows: 3 %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.submit_button "Add Transaction" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit Student</h1>
|
||||
|
||||
<%= render "form", student: @student %>
|
||||
</div>
|
||||
@@ -0,0 +1,144 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Students</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "Template", template_admin_students_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<%= link_to "Import Students", "#",
|
||||
onclick: "document.getElementById('import-modal').classList.remove('hidden'); return false;",
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<%= link_to "New Student", new_admin_student_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Tabs -->
|
||||
<% current_filter = current_discard_filter %>
|
||||
<div class="flex gap-1 border-b border-gray-200">
|
||||
<%= link_to "Active",
|
||||
admin_students_path,
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :active ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "Archived",
|
||||
admin_students_path(discarded: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :discarded ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "All",
|
||||
admin_students_path(all: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :all ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:username, "Username") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Classroom
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:created_at, "Created At") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @students.any? %>
|
||||
<% @students.each do |student| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= admin_student_path(student) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.username %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.classroom.name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.created_at.strftime("%B %d, %Y") %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_student_path(student), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<% unless student.discarded? %>
|
||||
<%= link_to "Edit", edit_admin_student_path(student), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<% end %>
|
||||
<%= discard_restore_action(student) %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="5" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No students found. Create your first student to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Modal -->
|
||||
<div id="import-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<!-- Background overlay -->
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="document.getElementById('import-modal').classList.add('hidden');"></div>
|
||||
|
||||
<!-- Modal content -->
|
||||
<div class="relative bg-white rounded-lg shadow-xl max-w-lg w-full mx-4 z-50">
|
||||
<div class="px-6 py-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900" id="modal-title">
|
||||
Import Students from CSV
|
||||
</h3>
|
||||
<button type="button" class="text-gray-400 hover:text-gray-500" onclick="document.getElementById('import-modal').classList.add('hidden');">
|
||||
<span class="sr-only">Close</span>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-500 mb-4">
|
||||
Upload a CSV file with columns: <strong>classroom_id, username</strong>
|
||||
</p>
|
||||
|
||||
<%= form_with url: import_admin_students_path, multipart: true, local: true do |form| %>
|
||||
<div class="mb-4">
|
||||
<%= form.file_field :csv_file,
|
||||
accept: ".csv",
|
||||
class: "block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none p-2" %>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3">
|
||||
<button type="button"
|
||||
onclick="document.getElementById('import-modal').classList.add('hidden');"
|
||||
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Cancel
|
||||
</button>
|
||||
<%= form.submit "Import Students",
|
||||
class: "inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New Student</h1>
|
||||
|
||||
<%= render "form", student: @student %>
|
||||
</div>
|
||||
@@ -0,0 +1,210 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @student.username %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_student_path(@student),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_student_path(@student),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to archive this student?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-box-archive -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Archive
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<dl class="divide-y divide-gray-200">
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Id</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.id %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Username</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.username %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Classroom</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.classroom.name %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Created at</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.created_at.strftime("%B %d, %Y") %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Updated at</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.updated_at.strftime("%B %d, %Y") %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Associated Portfolio -->
|
||||
<% if @student.portfolio.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio Details</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-blue-50 p-4 rounded-lg">
|
||||
<div class="text-xs font-medium text-blue-600 mb-1" data-testid="cash_balance_label">
|
||||
Cash Balance
|
||||
</div>
|
||||
<div class="text-2xl font-bold text-blue-900"><%= number_to_currency(@student.portfolio.cash_balance) %></div>
|
||||
</div>
|
||||
<div class="bg-green-50 p-4 rounded-lg">
|
||||
<div class="text-xs font-medium text-green-600 mb-1" data-testid="total_portfolio_worth_label">
|
||||
Total Portfolio Worth
|
||||
</div>
|
||||
<div class="text-2xl font-bold text-green-900"><%= number_to_currency(@student.portfolio.total_portfolio_worth) %></div>
|
||||
</div>
|
||||
<div class="bg-purple-50 p-4 rounded-lg">
|
||||
<div class="text-xs font-medium text-purple-600 mb-1">Total Stocks</div>
|
||||
<div class="text-2xl font-bold text-purple-900"><%= @student.portfolio.portfolio_stocks.sum(:shares).to_i %></div>
|
||||
</div>
|
||||
<%= link_to portfolio_path(@student.portfolio), class: "bg-gray-50 p-4 rounded-lg block hover:bg-gray-100 transition-colors" do %>
|
||||
<div class="text-xs font-medium text-gray-600 mb-1">Portfolio ID</div>
|
||||
<div class="text-2xl font-bold text-gray-900">#<%= @student.portfolio.id %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Earnings Summary -->
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-3">Earnings Summary</h4>
|
||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg mb-6">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Category</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500 uppercase">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">Attendance Earnings</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-gray-900"><%= number_to_currency(@earnings_summary.attendance_earnings_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">Math Earnings</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-gray-900"><%= number_to_currency(@earnings_summary.math_earnings_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">Rewards</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-gray-900"><%= number_to_currency(@earnings_summary.awards_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr class="bg-green-50">
|
||||
<td class="px-3 py-2 text-sm font-semibold text-gray-900">Total Earnings</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-bold text-gray-900"><%= number_to_currency(@earnings_summary.total_earnings_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm font-medium text-red-600">Transaction Fees</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-red-600"><%= number_to_currency(@earnings_summary.transaction_fees_cents / 100.0) %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Transactions -->
|
||||
<% transactions = @student.portfolio.portfolio_transactions.order(created_at: :desc) %>
|
||||
<% if transactions.any? %>
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-3">All Transactions (<%= transactions.count %>)</h4>
|
||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg mb-4">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Reason</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500 uppercase">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% transactions.each do |transaction| %>
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">
|
||||
<%= transaction.created_at.strftime("%m/%d/%Y") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= transaction.transaction_type == 'deposit' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' %>">
|
||||
<%= transaction.transaction_type.capitalize %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">
|
||||
<%= transaction.reason&.humanize || 'N/A' %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-right font-medium text-gray-900">
|
||||
<%= number_to_currency(transaction.amount_cents / 100.0) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500 mb-4">No transactions yet.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No portfolio associated with this student.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendance -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Attendance</h3>
|
||||
<% if @attendance_entries.any? %>
|
||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Year</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Quarter</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500 uppercase">Attendance Days</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500 uppercase">Perfect Attendance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% @attendance_entries.each do |entry| %>
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">
|
||||
<%= entry.year_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">
|
||||
<%= entry.quarter_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-right text-gray-900">
|
||||
<%= entry.attendance_days_display %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-center">
|
||||
<% if entry.perfect_attendance? %>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500">No attendance records found.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Students", admin_students_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
<%= form_with model: @teacher, url: @teacher.persisted? ? admin_teacher_path(@teacher) : admin_teachers_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Teacher Details</h3>
|
||||
|
||||
<%= f.text_field :email,
|
||||
label: "Email",
|
||||
hint: "Also used as the teacher's login username",
|
||||
placeholder: "teacher@example.com" %>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Full Name",
|
||||
hint: "Teacher's full name",
|
||||
placeholder: "John Doe" %>
|
||||
|
||||
<div data-controller="form-filter" data-form-filter-frame-id-value="teacher_classroom_selection">
|
||||
<%= f.select :school_id,
|
||||
options_from_collection_for_select(@schools, :id, :name, @selected_school_id),
|
||||
{ label: "School Filter", hint: "Select a school to filter the classroom list", include_blank: "All Schools" },
|
||||
{ name: "school_id", data: { action: "change->form-filter#change" } } %>
|
||||
</div>
|
||||
|
||||
<%= turbo_frame_tag "teacher_classroom_selection", class: "block mb-6" do %>
|
||||
<% if @classrooms.any? %>
|
||||
<%= f.collection_check_boxes :classroom_ids,
|
||||
@classrooms,
|
||||
:id,
|
||||
:name,
|
||||
label: "Classrooms",
|
||||
hint: "Select one or more classrooms to assign to this teacher",
|
||||
wrapper_class: "mb-0" %>
|
||||
<% else %>
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Classrooms</label>
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-800">No Classrooms Available</h3>
|
||||
<p class="text-sm text-yellow-700 mt-1">
|
||||
No Classrooms associated with this school and active year, please
|
||||
<%= link_to "update classrooms", admin_classrooms_path, class: "font-medium underline text-yellow-700 hover:text-yellow-600" %>
|
||||
so they will show up here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @teacher.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-600">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
A temporary password will be generated and a password reset email will be sent to the teacher's email address.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-x-4">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @teacher.persisted? ? admin_teacher_path(@teacher) : admin_teachers_path %>
|
||||
<%= f.submit_button @teacher.persisted? ? "Update Teacher" : "Create Teacher" %>
|
||||
</div>
|
||||
<% if @teacher.discarded? %>
|
||||
<%= link_to "Permanently Delete",
|
||||
admin_teacher_path(@teacher),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to permanently delete this teacher? This action cannot be undone." },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit Teacher</h1>
|
||||
|
||||
<%= render "form", teacher: @teacher %>
|
||||
</div>
|
||||
@@ -0,0 +1,114 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Teachers</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New Teacher", new_admin_teacher_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Tabs -->
|
||||
<% current_filter = current_discard_filter %>
|
||||
<div class="flex gap-1 border-b border-gray-200">
|
||||
<%= link_to "Active",
|
||||
admin_teachers_path,
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :active ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "Deactivated",
|
||||
admin_teachers_path(discarded: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :discarded ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "All",
|
||||
admin_teachers_path(all: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :all ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:username, "Username") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:email, "Email") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:name, "Name") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:created_at, "Created At") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @teachers.any? %>
|
||||
<% @teachers.each do |teacher| %>
|
||||
<tr id="<%= dom_id(teacher) %>" class="hover:bg-gray-50 transition-colors">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.username %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.email %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.created_at.strftime("%B %d, %Y") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm">
|
||||
<% if teacher.discarded? %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/10">Deactivated</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">Active</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_teacher_path(teacher), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_teacher_path(teacher), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<% if teacher.discarded? %>
|
||||
<%= link_to "Reactivate", admin_teacher_reactivation_path(teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to reactivate this teacher?" },
|
||||
class: "text-green-600 hover:text-green-800" %>
|
||||
<% else %>
|
||||
<%= link_to "Deactivate", admin_teacher_deactivation_path(teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to deactivate this teacher?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="7" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No teachers found. Create your first teacher to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New Teacher</h1>
|
||||
|
||||
<%= render "form", teacher: @teacher %>
|
||||
</div>
|
||||
@@ -0,0 +1,74 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @teacher.username %></h2>
|
||||
<% if @teacher.discarded? %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/10">Deactivated</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">Active</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_teacher_path(@teacher),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<% if @teacher.discarded? %>
|
||||
<%= link_to admin_teacher_reactivation_path(@teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to reactivate this teacher?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-green-300 shadow-sm text-sm font-medium rounded-md text-green-700 bg-white hover:bg-green-50" do %>
|
||||
<i class="fas fa-check-circle -ml-1 mr-2 h-5 w-5 text-green-500"></i>
|
||||
Reactivate
|
||||
<% end %>
|
||||
<%= link_to admin_teacher_path(@teacher),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to permanently delete this teacher? This action cannot be undone." },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Permanently Delete
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to admin_teacher_deactivation_path(@teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to deactivate this teacher?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Deactivate
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@teacher,
|
||||
attributes: %i[id username email name created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Classrooms -->
|
||||
<% if @teacher.classrooms.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Classrooms</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @teacher.classrooms.each do |classroom| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= classroom.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No classrooms assigned to this teacher yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Teachers", admin_teachers_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,62 @@
|
||||
<%= form_with model: @user, scope: :user, url: @user.persisted? ? admin_user_path(@user) : admin_users_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">User Details</h3>
|
||||
|
||||
<%= f.text_field :username,
|
||||
label: "Username",
|
||||
hint: "Unique username for the user",
|
||||
placeholder: "johndoe" %>
|
||||
|
||||
<%= f.select :type,
|
||||
options_for_select(
|
||||
%w[User Student Teacher].map { |t| [t, t] },
|
||||
@user.type
|
||||
),
|
||||
label: "User Type",
|
||||
hint: "Select the type of user (Student, Teacher, or generic User)",
|
||||
include_blank: false %>
|
||||
|
||||
<%= f.text_field :email,
|
||||
label: "Email",
|
||||
hint: "Email address (required for Teachers and Admins, optional for Students)",
|
||||
placeholder: "user@example.com" %>
|
||||
|
||||
<%= f.boolean_field :admin,
|
||||
label: "Admin",
|
||||
hint: "Grant admin privileges to this user" %>
|
||||
|
||||
<%= f.select :classroom_id,
|
||||
options_from_collection_for_select(
|
||||
Classroom.order(:name),
|
||||
:id,
|
||||
:name,
|
||||
@user.classroom_id
|
||||
),
|
||||
label: "Classroom",
|
||||
hint: "Optional classroom assignment",
|
||||
include_blank: "No classroom" %>
|
||||
|
||||
<% if @user.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Password</h4>
|
||||
|
||||
<%= f.password_field :password,
|
||||
label: "Password",
|
||||
hint: "Minimum 6 characters",
|
||||
placeholder: "••••••••" %>
|
||||
|
||||
<%= f.password_field :password_confirmation,
|
||||
label: "Password Confirmation",
|
||||
hint: "Re-enter password to confirm",
|
||||
placeholder: "••••••••" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @user.persisted? ? admin_user_path(@user) : admin_users_path %>
|
||||
<%= f.submit_button @user.persisted? ? "Update User" : "Create User" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit User</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @user.username %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,95 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Users</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New User", new_admin_user_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:username, "Username") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:type, "Type") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:email, "Email") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Classroom
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:admin, "Admin") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @users.any? %>
|
||||
<% @users.each do |user| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= user_show_path(user) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.username %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.type %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.email.presence || content_tag(:span, "—", class: "text-gray-400") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.classroom&.name || content_tag(:span, "—", class: "text-gray-400") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<% if user.admin? %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", user_show_path(user), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_user_path(user), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<%= link_to "Delete", admin_user_path(user),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="7" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No users found. Create your first user to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New User</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @user.username %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_user_path(@user),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_user_path(@user),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this user?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@user,
|
||||
attributes: %i[id username type email admin classroom created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Portfolio -->
|
||||
<% if @user.portfolio.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio</h3>
|
||||
<div class="text-sm text-gray-900">
|
||||
Portfolio ID: <%= @user.portfolio.id %>
|
||||
<% if @user.student? %>
|
||||
<%= link_to "View Portfolio", user_portfolio_path(@user, @user.portfolio), class: "ml-2 text-blue-600 hover:text-blue-800" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No portfolio associated with this user.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Users", admin_users_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<% content_for :title, @announcement.title %>
|
||||
|
||||
<div class="max-w-[1180px] mx-auto space-y-6 pb-16" style="font-family: Inter, system-ui, sans-serif;">
|
||||
<% if notice.present? %>
|
||||
<div class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-md inline-block" id="notice"><%= notice %></div>
|
||||
<% end %>
|
||||
|
||||
<!-- Announcement Content Card -->
|
||||
<div class="rounded-xl border-2 border-black bg-white overflow-hidden">
|
||||
<div class="bg-teal-500 text-white py-4 px-6 font-semibold text-lg tracking-wide border-b-2 border-black">
|
||||
<%= @announcement.title %>
|
||||
</div>
|
||||
<div class="p-8">
|
||||
<div class="trix-content text-gray-700 leading-relaxed">
|
||||
<%= @announcement.content %>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-gray-200">
|
||||
<%= link_to "Back", :back, class: "inline-flex items-center rounded-md px-4 py-2.5 bg-gray-100 hover:bg-gray-50 font-medium text-gray-700 transition-colors" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,86 @@
|
||||
<div class="mt-8">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Student [Username]</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Portfolio Value</th>
|
||||
<% if @can_manage_students %>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Available Cash</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<% @students.each do |student| %>
|
||||
<tr class="group hover:bg-gray-50 transition-colors duration-150"
|
||||
id="<%= dom_id(student) %>">
|
||||
<td class="px-6 py-4 whitespace-nowrap" data-testid="student-username">
|
||||
<% if student.username.present? %>
|
||||
<% if student.portfolio %>
|
||||
<%= link_to student.username, user_portfolio_path(student, student.portfolio), class: "text-black underline hover:text-[var(--sitf-secondary-chart2)] font-medium" %>
|
||||
<% else %>
|
||||
<span class="text-gray-400 italic"><%= student.username %></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="text-gray-400 italic">No username set</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">
|
||||
<%= number_to_currency(student.portfolio&.holdings_value || 0) %>
|
||||
</td>
|
||||
<% if @can_manage_students %>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">
|
||||
<%= number_to_currency(student.portfolio&.cash_balance || 0) %>
|
||||
</td>
|
||||
<% end %>
|
||||
<% if @can_manage_students %>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium" data-testid="student-actions">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= link_to edit_classroom_student_path(@classroom, student),
|
||||
class: "inline-flex items-center justify-center w-8 h-8 text-black hover:text-[var(--sitf-secondary-chart2)] hover:bg-blue-50 rounded-lg transition-all duration-150 group",
|
||||
title: "Edit student",
|
||||
data: { testid: "edit-student" } do %>
|
||||
<i class="fa fa-pencil fa-lg group-hover:scale-110 transition-transform duration-150"></i>
|
||||
<% end %>
|
||||
|
||||
<%= link_to reset_password_classroom_student_path(@classroom, student),
|
||||
data: {
|
||||
turbo_method: :patch,
|
||||
turbo_confirm: "Are you sure you want to reset #{student.username}'s password?",
|
||||
testid: "reset-password"
|
||||
},
|
||||
class: "inline-flex items-center justify-center w-8 h-8 text-amber-600 hover:text-amber-800 hover:bg-amber-50 rounded-lg transition-all duration-150 group",
|
||||
title: "Reset password" do %>
|
||||
<%= lucide_icon("refresh-cw") %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to classroom_student_path(@classroom, student),
|
||||
data: {
|
||||
turbo_method: :delete,
|
||||
turbo_confirm: "Are you sure you want to delete #{student.username}?",
|
||||
testid: "delete-student"
|
||||
},
|
||||
class: "inline-flex items-center justify-center w-8 h-8 text-red-600 hover:text-red-800 hover:bg-red-50 rounded-lg transition-all duration-150 group",
|
||||
title: "Delete student" do %>
|
||||
<i class="fa fa-trash fa-lg group-hover:scale-110 transition-transform duration-150"></i>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<% if @students.empty? %>
|
||||
<div class="text-center py-8">
|
||||
<p class="text-gray-500">No students in this classroom yet.</p>
|
||||
<% if @can_manage_students %>
|
||||
<%= link_to "Add the first student", new_classroom_student_path(@classroom),
|
||||
class: "mt-2 inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,131 @@
|
||||
<%= form_with(model: classroom, class: "contents") do |form| %>
|
||||
<% if classroom.errors.any? %>
|
||||
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||
<h2>
|
||||
<%= pluralize(classroom.errors.count, "error") %> prohibited this classroom from being saved:
|
||||
</h2>
|
||||
|
||||
<ul>
|
||||
<% classroom.errors.each do |error| %>
|
||||
<li><%= error.full_message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="my-5">
|
||||
<%= form.label :name, class: 'tw-label-primary' do %>
|
||||
Name<span class="required-indicator">*</span>
|
||||
<% end %>
|
||||
<div class="mt-2">
|
||||
<%= form.text_field :name, class: "tw-input-primary", required: true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-5">
|
||||
<%= form.label :school_id, "School Name", class: 'tw-label-primary' %>
|
||||
<div class="mt-2">
|
||||
<% school_year = classroom&.school_year
|
||||
selected_school_id = school_year&.school&.id %>
|
||||
<%= form.select :school_id,
|
||||
options_from_collection_for_select(@schools, :id, :name, selected_school_id),
|
||||
{ prompt: "Select a school" },
|
||||
{ class: "tw-input-primary" } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-5">
|
||||
<%= form.label :year_id, "Year", class: 'tw-label-primary' %>
|
||||
<div class="mt-2">
|
||||
<% school_year = classroom&.school_year
|
||||
selected_year_id = school_year&.year&.id %>
|
||||
<%= form.select :year_id,
|
||||
options_from_collection_for_select(@years, :id, :name, selected_year_id),
|
||||
{ prompt: "Select a year" },
|
||||
{ class: "tw-input-primary" } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-5">
|
||||
<%= form.label :grade_ids, class: 'tw-label-primary flex items-center gap-2' do %>
|
||||
Grades<span class="required-indicator">*</span>
|
||||
<% end %>
|
||||
|
||||
<!-- Single hidden field to handle empty selections -->
|
||||
<%= hidden_field_tag "classroom[grade_ids][]", "" %>
|
||||
|
||||
<div class="mt-3 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 max-h-60 overflow-y-auto p-3 border border-gray-200 rounded-lg bg-gray-50">
|
||||
<% Grade.where(level: Classroom::GRADE_RANGE).order(:level).each do |grade| %>
|
||||
<%# Check if this grade is selected for the classroom %>
|
||||
<% is_checked = classroom.persisted? ? classroom.grades.include?(grade) : false %>
|
||||
|
||||
<label class="flex items-center gap-3 p-3 hover:bg-white rounded-lg cursor-pointer transition-colors">
|
||||
<%= check_box_tag "classroom[grade_ids][]",
|
||||
grade.id,
|
||||
is_checked,
|
||||
class: "rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-gray-900">
|
||||
<%= grade.name %>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 mt-2">
|
||||
<span class="text-red-500 font-medium">* Required:</span> Select at least one grade that applies to this classroom.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="my-5">
|
||||
<div class="flex items-center gap-3">
|
||||
<%= form.check_box :trading_enabled, class: "rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
|
||||
<%= form.label :trading_enabled, "Enable Trading", class: 'tw-label-primary' %>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mt-1">When enabled, students in this classroom can place buy and sell orders.</p>
|
||||
</div>
|
||||
|
||||
<div class="my-5">
|
||||
<%= label_tag :teacher_ids, "Teachers", class: 'tw-label-primary flex items-center gap-2' %>
|
||||
|
||||
<!-- Single hidden field to handle empty selections -->
|
||||
<%= hidden_field_tag "classroom[teacher_ids][]", "" %>
|
||||
|
||||
<div class="mt-3 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-60 overflow-y-auto p-2 border border-gray-200 rounded-lg bg-gray-50">
|
||||
<% @teachers.each do |teacher| %>
|
||||
<%# Check if teacher is assigned to this classroom %>
|
||||
<% is_checked = classroom.persisted? ? classroom.teachers.include?(teacher) : false %>
|
||||
|
||||
<label class="flex items-start gap-2 p-3 hover:bg-white rounded-lg cursor-pointer transition-colors">
|
||||
<%= check_box_tag "classroom[teacher_ids][]",
|
||||
teacher.id,
|
||||
is_checked,
|
||||
{
|
||||
class: "rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-0.5",
|
||||
id: "classroom_teacher_ids_#{teacher.id}"
|
||||
} %>
|
||||
|
||||
<div class="flex items-start gap-2 min-w-0 flex-1">
|
||||
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white text-sm font-medium flex-shrink-0 mt-1">
|
||||
<%= teacher.display_name.first.upcase %>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-gray-900 font-medium truncate">
|
||||
<%= teacher.display_name %>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 truncate">
|
||||
@<%= teacher.email %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline">
|
||||
<%= form.submit class: "tw-btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="mt-8 w-64 flex-initial">
|
||||
<h3 class="bg-amber-300 py-3 px-5 font-medium text-sm tracking-wide rounded-t-xl">
|
||||
<i class="fas fa-book w-4 h-4"></i>
|
||||
Grade Books
|
||||
</h3>
|
||||
|
||||
<% if grade_books.any? %>
|
||||
<ul class="divide-y divide-gray-200 bg-white rounded-md shadow">
|
||||
<% grade_books.each do |book| %>
|
||||
<li class="px-6 py-4 hover:bg-gray-50">
|
||||
<%= link_to classroom_grade_book_path(@classroom, book),
|
||||
class: "text-black underline hover:text-[var(--sitf-secondary-chart2)] font-medium" do %>
|
||||
<span class="flex flex-between items-center justify-between w-full">
|
||||
<%= book.quarter.name %>
|
||||
<i class="fa-solid fa-arrow-right"></i>
|
||||
</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<div class="text-center py-8 text-gray-500">
|
||||
No grade books created for this classroom yet.
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="mx-auto md:w-2/3 w-full">
|
||||
<h1 class="font-bold text-4xl">Editing classroom</h1>
|
||||
|
||||
<%= render "form", classroom: @classroom %>
|
||||
|
||||
<%= link_to "Show this classroom", @classroom, class: "tw-btn-tertiary" %>
|
||||
<%= link_to "Back to classrooms", classrooms_path, class: "tw-btn-tertiary" %>
|
||||
</div>
|
||||
@@ -0,0 +1,102 @@
|
||||
<div class="flex flex-col h-full w-full">
|
||||
<% if notice.present? %>
|
||||
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice">
|
||||
<%= notice %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<header class="flex items-center justify-between px-6 py-4 border-b">
|
||||
<h1 class="text-2xl font-bold py-2">Classrooms</h1>
|
||||
<% if policy(Classroom).new? %>
|
||||
<%= link_to "New classroom", new_classroom_path,
|
||||
class: "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" %>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 p-6">
|
||||
<%= render "shared/table_container" do %>
|
||||
<thead>
|
||||
<tr class="table-header-row">
|
||||
<th class="table-header-cell table-header-cell-left">
|
||||
<%= sort_link(:name, "Name") %>
|
||||
</th>
|
||||
<th class="table-header-cell table-header-cell-left">
|
||||
Teacher(s)
|
||||
</th>
|
||||
<th class="table-header-cell table-header-cell-left">
|
||||
<%= sort_link(:student_count, "Student Count") %>
|
||||
</th>
|
||||
<th class="table-header-cell table-header-cell-right">
|
||||
<%= sort_link(:total_earnings, "Total Earnings") %>
|
||||
</th>
|
||||
<th class="table-header-cell table-header-cell-center">
|
||||
Edit
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if @classrooms.any? %>
|
||||
<% @classrooms.each do |classroom| %>
|
||||
<tr class="table-body-row">
|
||||
<td class="table-body-cell">
|
||||
<span class="table-cell-content">
|
||||
<%= link_to classroom.name, classroom_path(classroom), class: "text-black underline hover:text-[var(--sitf-secondary-chart2)]" %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="table-body-cell py-4">
|
||||
<div class="flex gap-1 overflow-x-auto py-1" style="max-width: 510px; min-height: 36px;">
|
||||
<% if classroom.teachers.any? %>
|
||||
<% classroom.teachers.each do |teacher| %>
|
||||
<span class="inline-flex items-center px-2.5 py-1.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 border border-blue-200 whitespace-nowrap flex-shrink-0">
|
||||
<i class="fas fa-user-tie mr-1 text-blue-600"></i>
|
||||
<%= teacher.display_name %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-500 border border-gray-200 whitespace-nowrap">
|
||||
<i class="fas fa-user-slash mr-1 text-gray-400"></i>
|
||||
No teacher assigned
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-body-cell">
|
||||
<span class="table-cell-content">
|
||||
<%= classroom.students.count %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="table-body-cell table-body-cell-right">
|
||||
<span class="table-cell-content">
|
||||
$<%= number_with_precision(classroom.students.sum { |s| s.portfolio&.calculate_total_value || 0 }, precision: 2) %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="table-body-cell table-body-cell-center">
|
||||
<% if policy(classroom).edit? %>
|
||||
<%= render partial: "components/action_icon_button",
|
||||
locals: {
|
||||
options: {
|
||||
path: edit_classroom_path(classroom),
|
||||
icon: "fa-pencil",
|
||||
title: "Edit classroom",
|
||||
aria_label: "Edit classroom",
|
||||
text_class: "text-black"
|
||||
}
|
||||
} %>
|
||||
<% else %>
|
||||
<span class="table-no-permission">-</span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr class="table-body-row">
|
||||
<td class="table-empty-cell" colspan="5">
|
||||
<p class="text-lg">No classrooms found</p>
|
||||
<p class="mt-2">Create your first classroom to get started!</p>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
<% end %>
|
||||
</main>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="mx-auto md:w-2/3 w-full">
|
||||
<h1 class="font-bold text-4xl">New classroom</h1>
|
||||
|
||||
<%= render "form", classroom: @classroom %>
|
||||
|
||||
<%= link_to "Back to classrooms", classrooms_path, class: "tw-btn-tertiary" %>
|
||||
</div>
|
||||
@@ -0,0 +1,46 @@
|
||||
<div class="mx-auto w-full flex">
|
||||
<div class="mx-auto w-full">
|
||||
<% if notice.present? %>
|
||||
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||
<% end %>
|
||||
|
||||
<div class="flex gap-4 items-center justify-between my-6">
|
||||
<div class="flex gap-4">
|
||||
<h1 class="text-3xl font-bold">
|
||||
<%= [
|
||||
@classroom.name,
|
||||
("Grade #{@classroom.grades_display}" if @classroom.grades_display.present?),
|
||||
@classroom.year_name
|
||||
].compact.join(', ') %>
|
||||
</h1>
|
||||
|
||||
<%= form_with url: toggle_trading_classroom_path(@classroom), method: :patch, class: "contents" do %>
|
||||
<label class="inline-flex items-center cursor-pointer">
|
||||
<%= check_box_tag "trading", "1", @classroom.trading_enabled?, class: "sr-only peer", disabled: !policy(@classroom).toggle_trading?, onchange: "this.form.requestSubmit()" %>
|
||||
<span class="select-none text-sm font-medium">Trading:</span>
|
||||
<div class="relative ms-1 w-9 h-5 bg-gray-300 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-buffer after:content-[''] after:absolute after:top-0.5 after:start-0.5 after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-green-600"></div>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @can_manage_students %>
|
||||
<div class="flex justify-end gap-3">
|
||||
<%= link_to "Add Student", new_classroom_student_path(@classroom),
|
||||
class: "tw-btn-primary" %>
|
||||
<% if policy(@classroom).edit? %>
|
||||
<%= link_to "Edit Class", edit_classroom_path(@classroom), class: "tw-btn-secondary !rounded-lg !py-3 !px-5 !text-base !text-black !ring-black" %>
|
||||
<% end %>
|
||||
<!-- TODO: Add bulk functionality in future PR -->
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-8">
|
||||
<div class="flex-1">
|
||||
<%= render 'classroom_students_table', students: @classroom.users %>
|
||||
</div>
|
||||
<!-- Temporary UI, remove when tabbed table is wired up: -->
|
||||
<%= render "grade_books_list", grade_books: @classroom.grade_books %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<% if options[:method] %>
|
||||
<%= button_to options[:path], method: options[:method], data: options[:data] || {}, class: "inline-flex items-center justify-center w-6 h-6 sm:w-8 sm:h-8 #{options[:text_class] || 'text-black'} hover:text-[var(--sitf-secondary-chart2)] hover:bg-blue-50 rounded-lg transition-all duration-150 group", title: options[:title], aria: { label: options[:aria_label] } do %>
|
||||
<i class="fa <%= options[:icon] %> fa-sm sm:fa-lg group-hover:scale-110 transition-transform duration-150"></i>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to options[:path], class: "inline-flex items-center justify-center w-6 h-6 sm:w-8 sm:h-8 #{options[:text_class] || 'text-black'} hover:text-[var(--sitf-secondary-chart2)] hover:bg-blue-50 rounded-lg transition-all duration-150 group", title: options[:title], aria: { label: options[:aria_label] }, data: options[:data] || {} do %>
|
||||
<i class="fa <%= options[:icon] %> fa-sm sm:fa-lg group-hover:scale-110 transition-transform duration-150"></i>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!--prettier-ignore-->
|
||||
<% case as %>
|
||||
<% when :link %>
|
||||
<%= link_to href, class: button_classes, data: data do %>
|
||||
<%= text %>
|
||||
<% end %>
|
||||
<% when :button %>
|
||||
<%= button_tag class: button_classes, data: data do %>
|
||||
<%= text %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,31 @@
|
||||
<div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
role="checkbox"
|
||||
aria-checked="false"
|
||||
data-state="unchecked"
|
||||
value="on"
|
||||
class="peer h-4 w-4 shrink-0 rounded-xs border border-primary ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-3 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
|
||||
name="<%= name %>"
|
||||
id="<%= options[:id] || sanitize_to_id(name) %>">
|
||||
<span
|
||||
class="flex items-center justify-center text-current hidden"
|
||||
style="pointer-events: none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="h-4 w-4">
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
</span>
|
||||
<%= render_label name: name, label: label %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
<%= text_field_tag name, value, type: type, id: id,
|
||||
class: options[:class],
|
||||
placeholder: options[:placeholder],
|
||||
autocomplete: options[:autocomplete],
|
||||
autocapitalize: options[:autocapitalize],
|
||||
autocorrect: options[:autocorrect],
|
||||
disabled: options[:disabled],
|
||||
required: options[:required],
|
||||
readonly: options[:readonly],
|
||||
data: options[:data] %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= label_tag name, label,
|
||||
data: options[:data],
|
||||
class: tw("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 #{options[:class]}") %>
|
||||
@@ -0,0 +1,9 @@
|
||||
<textarea
|
||||
class="flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-3 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 <%= options[:class] %>"
|
||||
placeholder="<%= options[:placeholder] %>"
|
||||
name="<%= name %>"
|
||||
id="<% id %>"
|
||||
<%= options[:disabled] ? "disabled" : "" %>
|
||||
<%= options[:required] ? "required" : "" %>
|
||||
<%= options[:readonly] ? "readonly" : "" %>
|
||||
<%= options[:rows] ? "rows" : "" %>></textarea>
|
||||
@@ -0,0 +1,16 @@
|
||||
<h2>Resend confirmation instructions</h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :email %><br>
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Resend confirmation instructions" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
@@ -0,0 +1,5 @@
|
||||
<p>Welcome <%= @email %>!</p>
|
||||
|
||||
<p>You can confirm your account email through the link below:</p>
|
||||
|
||||
<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
|
||||
@@ -0,0 +1,7 @@
|
||||
<p>Hello <%= @email %>!</p>
|
||||
|
||||
<% if @resource.try(:unconfirmed_email?) %>
|
||||
<p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
|
||||
<% else %>
|
||||
<p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<p>Hello <%= @resource.email %>!</p>
|
||||
|
||||
<p>We're contacting you to notify you that your password has been changed.</p>
|
||||
@@ -0,0 +1,5 @@
|
||||
<p>Hello <%= @resource.email %>!</p>
|
||||
|
||||
<p>You have a new account created by an admin. Click on the link to login to your account and get started.</p>
|
||||
|
||||
<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
|
||||
@@ -0,0 +1,7 @@
|
||||
<p>Hello <%= @resource.email %>!</p>
|
||||
|
||||
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
|
||||
|
||||
<p>Click the link below to unlock your account:</p>
|
||||
|
||||
<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>
|
||||
@@ -0,0 +1,25 @@
|
||||
<h2>Change your password</h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
<%= f.hidden_field :reset_password_token %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password, "New password" %><br>
|
||||
<% if @minimum_password_length %>
|
||||
<em>(<%= @minimum_password_length %> characters minimum)</em><br>
|
||||
<% end %>
|
||||
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password_confirmation, "Confirm new password" %><br>
|
||||
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Change my password" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<div class="flex w-full justify-center">
|
||||
<%= image_tag "SITF-caponly-Logo.svg", class: "navbar-logo", alt: "Stocks in The Future" %>
|
||||
</div>
|
||||
|
||||
<h2 class="text-center text-2xl/9 font-bold tracking-tight text-gray-900 pb-4">Forgot your password?</h2>
|
||||
<p class="text-center text-sm text-gray-500 pb-4">Enter your email and we'll send you reset instructions.</p>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="space-y-4">
|
||||
<%= f.email_field :email,
|
||||
autofocus: true,
|
||||
autocomplete: "email",
|
||||
placeholder: "Email address",
|
||||
class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 shadow-xs focus-visible:outline-hidden focus-visible:ring-3 focus-visible:ring-offset-2 focus-visible:border-muted" %>
|
||||
|
||||
<%= f.submit "Send me reset password instructions",
|
||||
class: "flex w-full justify-center rounded-md bg-[#00698C] px-3 py-1.5 text-sm/6 text-white shadow-xs hover:bg-blue-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 cursor-pointer" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-gray-500">
|
||||
<%= link_to "Back to sign in", new_session_path(resource_name), class: "font-semibold text-black underline hover:text-[var(--sitf-secondary-chart2)]" %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
<h2>Edit <%= resource_name.to_s.humanize %></h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :email %><br>
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
||||
</div>
|
||||
|
||||
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
|
||||
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
|
||||
<% end %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br>
|
||||
<%= f.password_field :password, autocomplete: "new-password" %>
|
||||
<% if @minimum_password_length %>
|
||||
<br>
|
||||
<em><%= @minimum_password_length %> characters minimum</em>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password_confirmation %><br>
|
||||
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br>
|
||||
<%= f.password_field :current_password, autocomplete: "current-password" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Update" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h3>Cancel my account</h3>
|
||||
|
||||
<div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>
|
||||
|
||||
<%= link_to "Back", :back %>
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div class="max-w-sm mx-auto space-y-6">
|
||||
<div class="space-y-2 text-center">
|
||||
<h1 class="text-3xl font-bold">Sign Up</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">Enter your email below to create an account.</p>
|
||||
</div>
|
||||
<%= render_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<%= f.label :username, label: "Username" %>
|
||||
<%= f.text_field :username, autofocus: true, autocomplete: "username" %>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<%= f.label :password %>
|
||||
<%= f.password_field :password, autocomplete: "new-password" %>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<%= f.label :password_confirmation %>
|
||||
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||
</div>
|
||||
<%= render_button "Sign Up", class: "w-full", type: "submit" %>
|
||||
<%= render_button "Sign In", href: new_session_path(resource_name), variant: :ghost, class: "inline-block w-full text-sm text-center underline", as: :link %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<div class="flex w-full justify-center">
|
||||
<%= image_tag "SITF-caponly-Logo.svg", class: "navbar-logo", alt: "Stocks in The Future" %>
|
||||
</div>
|
||||
|
||||
<h2 class="text-center text-2xl/9 font-bold tracking-tight text-gray-900 pb-4">Sign in to your account</h2>
|
||||
|
||||
<%= render_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
||||
<div class="space-y-4">
|
||||
<%= f.text_field :username, placeholder: "Username", autocomplete: "username", class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6" %>
|
||||
<%= f.password_field :password, placeholder: "Password", autocomplete: "current-password", class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6" %>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<% if devise_mapping.rememberable? %>
|
||||
<%= render_checkbox name: "user[remember_me]", label: "Remember Me" %>
|
||||
<% end %>
|
||||
|
||||
<%= render_button "Forgot your password?", href: new_password_path(resource_name), variant: :ghost, class: "text-sm font-semibold text-black underline hover:text-[var(--sitf-secondary-chart2)]", as: :link %>
|
||||
</div>
|
||||
|
||||
<%= render_button "Sign in", class: "flex w-full justify-center rounded-md bg-[#00698C] px-3 py-1.5 text-sm/6 text-white shadow-xs hover:bg-blue-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600", type: "submit" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<% if resource.errors.any? %>
|
||||
<div id="error_explanation" data-turbo-cache="false">
|
||||
<h2>
|
||||
<%= I18n.t(
|
||||
"errors.messages.not_saved",
|
||||
count: resource.errors.count,
|
||||
resource: resource.class.model_name.human.downcase
|
||||
) %>
|
||||
</h2>
|
||||
<ul>
|
||||
<% resource.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,25 @@
|
||||
<%- if controller_name != 'sessions' %>
|
||||
<%= link_to "Log in", new_session_path(resource_name) %><br>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
||||
<%= link_to "Sign up", new_registration_path(resource_name) %><br>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
||||
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
||||
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br>
|
||||
<% end %>
|
||||
|
||||
<%- if devise_mapping.omniauthable? %>
|
||||
<%- resource_class.omniauth_providers.each do |provider| %>
|
||||
<%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %><br>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,16 @@
|
||||
<h2>Resend unlock instructions</h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :email %><br>
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Resend unlock instructions" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
@@ -0,0 +1 @@
|
||||
<%= link_to t('.portfolio'), field.path %>
|
||||
@@ -0,0 +1,9 @@
|
||||
<%= button_to "Finalize Grades", finalize_classroom_grade_book_path(@classroom, @grade_book),
|
||||
disabled: @grade_book.completed?,
|
||||
class: @grade_book.completed? ? "tw-btn-primary-disabled" : "tw-btn-primary",
|
||||
data: {
|
||||
autosave_target: "button",
|
||||
turbo_frame: "_top",
|
||||
turbo_confirm: "Are you sure you want to finalize these grades? This action cannot be undone."
|
||||
},
|
||||
form: { id: "finalize-button" } %>
|
||||
@@ -0,0 +1,38 @@
|
||||
<tr id="<%= dom_id(entry) %>">
|
||||
<%= fields_for "grade_entries[]", entry do |f| %>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<%= entry.username.presence || content_tag(:span, "No username set", class: "text-gray-400 italic") %>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<%= f.select :math_grade,
|
||||
options_for_select(GradeEntry::GRADE_OPTIONS, entry.math_grade),
|
||||
{ include_blank: true },
|
||||
disabled:,
|
||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
|
||||
data: { testid: "math-grade-select" } %>
|
||||
</td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<%= f.select :reading_grade,
|
||||
options_for_select(GradeEntry::GRADE_OPTIONS, entry.reading_grade),
|
||||
{ include_blank: true },
|
||||
disabled:,
|
||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
|
||||
data: { testid: "reading-grade-select" } %>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<%= f.number_field :attendance_days,
|
||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
|
||||
disabled:,
|
||||
min: 0,
|
||||
data: { testid: "attendance-days-input" } %>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<%= f.checkbox :is_perfect_attendance,
|
||||
disabled:,
|
||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
|
||||
data: { testid: "perfect-attendance-checkbox" } %>
|
||||
</td>
|
||||
|
||||
<% end %>
|
||||
</tr>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div id="submit-button">
|
||||
<%= button_tag "Save Grades",
|
||||
type: "submit",
|
||||
disabled: @grade_book.completed?,
|
||||
class: @grade_book.completed? ? "tw-btn-primary-disabled" : "tw-btn-primary",
|
||||
data: { autosave_target: "button" } %>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Student</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Math Grade</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Reading Grade</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Attendance Days</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Perfect Attendance?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<%= render partial: "grade_entry", collection: grade_book.grade_entries.sort_by { it.user.username }, as: :entry, locals: { disabled: grade_book.completed? } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<% if grade_book.grade_entries.empty? %>
|
||||
<div class="text-center py-8">
|
||||
<p class="text-gray-500">No grade entries for this grade book yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,39 @@
|
||||
<% if notice.present? %>
|
||||
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||
<% end %>
|
||||
<% if alert.present? %>
|
||||
<p class="py-2 px-3 bg-red-50 mb-5 text-red-500 font-medium rounded-lg inline-block" id="alert"><%= alert %></p>
|
||||
<% end %>
|
||||
|
||||
<%= turbo_frame_tag @grade_book do %>
|
||||
<div class="mt-8"
|
||||
<% unless @grade_book.completed? %>
|
||||
data-controller="autosave"
|
||||
data-autosave-interval-value="30000"
|
||||
<% end %>>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Grade Book</h3>
|
||||
<div data-autosave-target="status" class="text-sm text-gray-500"></div>
|
||||
</div>
|
||||
|
||||
<%= form_with model: [@classroom, @grade_book],
|
||||
url: classroom_grade_book_path(@classroom, @grade_book),
|
||||
method: :patch,
|
||||
html: { data: { autosave_target: "form" } } do |f| %>
|
||||
|
||||
<%= render partial: "table", locals: { grade_book: @grade_book } %>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<%= render partial: "submit_button" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if current_user.admin? %>
|
||||
<div class="flex flex-col items-center pt-4">
|
||||
<%= render partial: "finalize_button" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= turbo_stream.replace "submit-button", partial: "grade_books/submit_button" %>
|
||||
<%= turbo_stream.replace "finalize-button", partial: "grade_books/finalize_button" %>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<%# Home Dashboard Page - aligns with portfolio card styling and app color palette %>
|
||||
<div class="max-w-[1180px] mx-auto space-y-6 pb-16" style="font-family: Inter, system-ui, sans-serif;">
|
||||
<!-- Welcome / Funds Banner (Hero) -->
|
||||
<div class="relative mb-8 mt-10">
|
||||
<div class="hero-banner rounded-[16px] border-2 border-black bg-gradient-to-r from-[#F4D18D] to-[#F8DBA8] px-4 md:px-6 py-2 md:py-3 flex flex-col md:flex-row gap-2 md:gap-3 items-start md:items-center justify-between relative">
|
||||
<!-- Decorative coins scattered in background -->
|
||||
<div class="flex-1 flex flex-col gap-1 pr-0 md:pr-6 relative z-10">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-4">
|
||||
<% if current_user&.student? && current_user.portfolio %>
|
||||
<% if current_user.portfolio.cash_balance > 0 %>
|
||||
<div class="funds-pill inline-flex items-center gap-1 rounded-full border-2 border-black bg-white py-1 px-8 md:px-12 text-xs md:text-sm font-medium shadow-md relative tracking-tight">
|
||||
<%= image_tag 'party_popper.png', class: 'object-contain absolute w-6 h-6 md:w-8 md:h-8', style: 'margin-left: -2rem; top: -0.75rem;', alt: 'Party popper celebration' %>
|
||||
<span class="leading-snug">You have <strong><%= number_to_currency(current_user.portfolio.cash_balance) %></strong> to invest! Lets Get Trading!</span>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="funds-pill inline-flex items-center gap-1 rounded-full border-2 border-black bg-white py-1 px-8 md:px-12 text-xs md:text-sm font-medium shadow-md relative tracking-tight">
|
||||
<span class="leading-snug">Sorry, you don't have any earnings to invest yet</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<h1 class="text-[20px] md:text-[24px] font-bold tracking-tight leading-tight mt-1 text-gray-900">WELCOME TO YOUR FINANCIAL JOURNEY!</h1>
|
||||
<p class="text-xs md:text-sm text-gray-800 mt-1 font-medium">This Is Your Launchpad To Earn, Invest, And Grow.</p>
|
||||
</div>
|
||||
<!-- Piggy Bank Illustration -->
|
||||
<div class="hidden sm:block absolute -bottom-8 right-4 md:right-8 z-20">
|
||||
<%= image_tag 'piggy_bank.png', class: 'w-24 md:w-32 lg:w-36 xl:w-40 h-auto object-contain', alt: 'Piggy bank with coins' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Middle Two-Column Section: Images (left) + Announcements (right) -->
|
||||
<div class="grid grid-cols-1 gap-4 lg:gap-8 lg:grid-cols-2 items-stretch mt-6">
|
||||
<!-- Left Images -->
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="flex gap-4 justify-center items-center w-full">
|
||||
<%= image_tag 'girl_skateboarding_holding_laptop.png', class: 'w-full max-w-48 md:max-w-56 lg:max-w-64 h-auto object-contain pointer-events-none select-none mx-auto -mt-8 relative z-10', alt: 'SITF Home Teens' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Announcements -->
|
||||
<div class="flex ml-0 relative z-20">
|
||||
<div class="rounded-xl border-2 border-black overflow-hidden bg-white w-full flex flex-col" role="region" aria-label="Announcements">
|
||||
<div class="bg-teal-500 text-white py-1.5 px-3 font-semibold text-xs tracking-wide border-b-2 border-black">Announcements</div>
|
||||
<div class="p-4 text-xs text-gray-600 flex flex-col">
|
||||
<% if @current_announcement %>
|
||||
<% cache @current_announcement do %>
|
||||
<div class="space-y-3">
|
||||
<h3 class="font-semibold text-gray-900 text-sm"><%= @current_announcement.title %></h3>
|
||||
<div class="text-gray-700 trix-content max-h-[480px] overflow-y-auto">
|
||||
<%= @current_announcement.content %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="italic text-gray-400">No announcements yet.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Getting Started Full Width -->
|
||||
<div class="rounded-xl border-2 border-black bg-white overflow-hidden">
|
||||
<div class="bg-[#d3df44] py-3 px-5 font-semibold text-base tracking-wide border-b-2 border-black">Getting Started</div>
|
||||
<div class="px-4 pt-4 pb-6 grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<% steps = [
|
||||
{ num: '1', title: 'Earn Funds', body: 'Show Up, Stay Engaged, And Earn Investment Funds Based On Your Attendance, Participation, And Performance.' },
|
||||
{ num: '2', title: 'Go To The Trading Floor', body: "Once You Have Funds, Explore The Trading Floor And The Company's To Buy Your First Stock." },
|
||||
{ num: '3', title: 'Buy & Sell', body: 'Choose Your Companies Wisely! You Can Buy And Sell As Your Portfolio Grows. Watch Your Earnings Change Based On The Market.' },
|
||||
{ num: '4', title: 'Track Your Progress', body: 'Your Portfolio Shows How Your Investments Are Performing Over Time, Plus Summaries Of Your Grades And Attendance.' }
|
||||
] %>
|
||||
<% steps.each do |s| %>
|
||||
<div class="flex flex-cols-2 gap-3 md:gap-2 items-start">
|
||||
<div class="pr-1 flex-none"><%= image_tag "#{s[:num]}_Number.png", alt: "Step #{s[:num]}", class: "h-10 md:h-14 lg:h-16" %></div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-xl font-semibold leading-snug"><%= s[:title] %></p>
|
||||
<p class="text-[13px] text-gray-700 leading-snug"><%= s[:body] %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Future ideas: render partials for announcements, dynamic steps, and maybe a quick links panel %>
|
||||
@@ -0,0 +1,170 @@
|
||||
<!-- Hidden checkbox for menu toggle (CSS-only, no JavaScript!) -->
|
||||
<input type="checkbox" id="mobile-menu-toggle" class="hidden peer">
|
||||
|
||||
<!-- Overlay - shows when checkbox is checked -->
|
||||
<label
|
||||
for="mobile-menu-toggle"
|
||||
class="fixed inset-0 bg-black/50 z-40 hidden peer-checked:block lg:hidden"></label>
|
||||
|
||||
<!-- Sidebar - slides in when checkbox is checked -->
|
||||
<nav class="fixed z-50 top-0 bottom-0 left-0 w-64
|
||||
transform -translate-x-full peer-checked:translate-x-0
|
||||
lg:translate-x-0 lg:top-16 lg:w-50
|
||||
transition-transform duration-300 ease-in-out">
|
||||
<div class="flex flex-col h-full overflow-y-auto px-0 pt-16 lg:pt-4 pb-4 bg-[#00698c]">
|
||||
|
||||
<!-- Close button (mobile only) -->
|
||||
<label
|
||||
for="mobile-menu-toggle"
|
||||
class="lg:hidden absolute top-4 right-4
|
||||
flex items-center justify-center w-10 h-10
|
||||
text-white hover:bg-white/10 rounded-lg
|
||||
cursor-pointer transition-colors">
|
||||
<!-- X icon -->
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</label>
|
||||
|
||||
<nav class="flex flex-1 flex-col">
|
||||
<ul role="list" class="flex flex-1 flex-col gap-y-1 px-3">
|
||||
<li>
|
||||
<ul role="list" class="space-y-1">
|
||||
<!-- Home link -->
|
||||
<li>
|
||||
<% is_active = request.path == root_path %>
|
||||
<label for="mobile-menu-toggle" class="lg:cursor-default cursor-pointer">
|
||||
<%= link_to root_path, class: "flex items-center justify-start px-3 py-3 rounded-lg w-full group hover:bg-black/20 #{'bg-[#d3df44]' if is_active}", style: is_active ? "background-color: #d3df44;" : "" do %>
|
||||
<div class="flex gap-3 items-center">
|
||||
<div class="w-6 h-6">
|
||||
<%= image_tag "house.svg", class: "w-6 h-6", style: "filter: brightness(0) saturate(100%) #{is_active ? 'invert(20%) sepia(8%) saturate(348%) hue-rotate(114deg) brightness(98%) contrast(85%)' : 'invert(93%) sepia(13%) saturate(343%) hue-rotate(31deg) brightness(115%) contrast(90%)'};" %>
|
||||
</div>
|
||||
<span class="font-medium text-sm" style="color: <%= is_active ? '#323232' : 'white' %>; font-family: Inter, sans-serif;">Home</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<!-- Portfolio link -->
|
||||
<% if current_user.portfolio && policy(current_user.portfolio).show? %>
|
||||
<li>
|
||||
<% is_active = request.path.include?('portfolios') %>
|
||||
<label for="mobile-menu-toggle" class="lg:cursor-default cursor-pointer">
|
||||
<%= link_to user_portfolio_path(current_user, current_user.portfolio), class: "flex items-center justify-start px-3 py-3 rounded-lg w-full group hover:bg-black/20 #{'bg-[#d3df44]' if is_active}", style: is_active ? "background-color: #d3df44;" : "" do %>
|
||||
<div class="flex gap-3 items-center">
|
||||
<div class="w-6 h-6">
|
||||
<%= image_tag "id-card.svg", class: "w-6 h-6", style: "filter: brightness(0) saturate(100%) #{is_active ? 'invert(20%) sepia(8%) saturate(348%) hue-rotate(114deg) brightness(98%) contrast(85%)' : 'invert(93%) sepia(13%) saturate(343%) hue-rotate(31deg) brightness(115%) contrast(90%)'};" %>
|
||||
</div>
|
||||
<span class="font-medium text-sm" style="color: <%= is_active ? '#323232' : 'white' %>; font-family: Inter, sans-serif;">My Portfolio</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</label>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<!-- Transactions link -->
|
||||
<li>
|
||||
<% is_active = request.path.include?('orders') %>
|
||||
<label for="mobile-menu-toggle" class="lg:cursor-default cursor-pointer">
|
||||
<%= link_to orders_path, class: "flex items-center justify-start px-3 py-3 rounded-lg w-full group hover:bg-black/20", style: is_active ? "background-color: #d3df44;" : "" do %>
|
||||
<div class="flex gap-3 items-center">
|
||||
<div class="w-6 h-6">
|
||||
<%= image_tag "receipt.svg", class: "w-6 h-6", style: "filter: brightness(0) saturate(100%) #{is_active ? 'invert(20%) sepia(8%) saturate(348%) hue-rotate(114deg) brightness(98%) contrast(85%)' : 'invert(100%)'}" %>
|
||||
</div>
|
||||
<span class="font-medium text-sm" style="color: <%= is_active ? '#323232' : 'white' %>; font-family: Inter, sans-serif;">Transactions</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<!-- Classes link -->
|
||||
<% if policy(Classroom).index? %>
|
||||
<li>
|
||||
<% is_active = request.path.include?('classrooms') && request.path.exclude?('admin') %>
|
||||
<label for="mobile-menu-toggle" class="lg:cursor-default cursor-pointer">
|
||||
<%= link_to classrooms_path, class: "flex items-center justify-start px-3 py-3 rounded-lg w-full group hover:bg-black/20", style: is_active ? "background-color: #d3df44;" : "" do %>
|
||||
<div class="flex gap-3 items-center">
|
||||
<div class="w-6 h-6">
|
||||
<svg class="w-6 h-6" style="color: <%= is_active ? '#323232' : 'white' %>;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="font-medium text-sm" style="color: <%= is_active ? '#323232' : 'white' %>; font-family: Inter, sans-serif;">Classes</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</label>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<!-- Trading Floor with collapsible stocks -->
|
||||
<li>
|
||||
<details <%= 'open' if request.path.include?('stocks') %> data-controller="stock-navbar-toggle">
|
||||
<summary class="flex items-center justify-between px-3 py-3 rounded-lg w-full list-none cursor-pointer <%= 'bg-[#d3df44]' if request.path.include?('stocks') %>" style="<%= request.path.include?('stocks') ? 'background-color: #d3df44;' : '' %>">
|
||||
<% is_active = request.path.include?('stocks') %>
|
||||
<%= link_to stocks_path, class: "flex gap-3 items-center flex-1 hover:opacity-80", data: { action: "click->stock-navbar-toggle#navigateLink" } do %>
|
||||
<div class="w-6 h-6">
|
||||
<%= image_tag "chart-no-axes-combined.svg", class: "w-6 h-6", style: "filter: brightness(0) saturate(100%) #{is_active ? 'invert(20%) sepia(8%) saturate(348%) hue-rotate(114deg) brightness(98%) contrast(85%)' : 'invert(100%)'}" %>
|
||||
</div>
|
||||
<span class="font-medium text-sm" style="color: <%= is_active ? '#323232' : 'white' %>; font-family: Inter, sans-serif;">Trading Floor</span>
|
||||
<% end %>
|
||||
|
||||
<div class="w-4 h-4 flex items-center justify-center cursor-pointer hover:opacity-70" data-action="click->stock-navbar-toggle#toggleChevron">
|
||||
<i class="fa-solid fa-angle-down icon-down" style="color: <%= is_active ? '#323232' : 'white' %>;"></i>
|
||||
<i class="fa-solid fa-angle-up icon-up" style="color: <%= is_active ? '#323232' : 'white' %>;"></i>
|
||||
</div>
|
||||
</summary>
|
||||
|
||||
<div class="box-border content-stretch flex flex-col items-start justify-start p-2 ml-6">
|
||||
<% stocks.each do |stock| %>
|
||||
<div class="content-stretch flex items-center justify-start relative rounded-2xl w-full">
|
||||
<% stock_active = request.path == stock_path(stock) %>
|
||||
<label for="mobile-menu-toggle" class="lg:cursor-default cursor-pointer w-full">
|
||||
<%= link_to stock_path(stock), class: "flex gap-2 grow min-h-[44px] items-center justify-start px-3 py-2 rounded-2xl hover:bg-black/20 w-full" do %>
|
||||
<div class="font-['Geist',sans-serif] font-normal text-sm text-white <%= 'underline decoration-2 underline-offset-2' if stock_active %>">
|
||||
<p class="leading-none"><%= stock.ticker %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Bottom section (Admin, Logout) -->
|
||||
<div class="flex flex-col items-center justify-start w-full mt-auto">
|
||||
<div class="h-px w-full bg-[#004f6b]"></div>
|
||||
<div class="flex flex-col items-start justify-start w-full py-2">
|
||||
|
||||
<% if policy(Classroom).new? %>
|
||||
<% is_active = request.path.include?('admin') %>
|
||||
<label for="mobile-menu-toggle" class="lg:cursor-default cursor-pointer w-full">
|
||||
<%= link_to admin_classrooms_path, class: "flex items-center gap-x-4 px-6 py-3 text-sm font-semibold hover:bg-black/20 w-full", style: is_active ? "background-color: #d3df44;" : "" do %>
|
||||
<svg class="w-5 h-5" style="color: <%= is_active ? '#323232' : 'white' %>;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a6.759 6.759 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
<span style="color: <%= is_active ? '#323232' : 'white' %>; font-family: Inter, sans-serif;">Admin</span>
|
||||
<% end %>
|
||||
</label>
|
||||
<% end %>
|
||||
|
||||
<label for="mobile-menu-toggle" class="lg:cursor-default cursor-pointer w-full">
|
||||
<%= link_to destroy_user_session_path, data: { turbo_method: :delete }, class: "flex items-center gap-x-4 px-6 py-3 text-sm font-semibold hover:bg-black/20 w-full" do %>
|
||||
<svg class="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15M12 9l-3 3m0 0 3 3m-3-3h12.75" />
|
||||
</svg>
|
||||
<span class="text-white" style="font-family: Inter, sans-serif;">Logout</span>
|
||||
<% end %>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="ticker-item">
|
||||
<div class="text-lg font-bold <%= stock.percentage_change >= 0 ? 'text-green-up' : 'text-destructive' %>">
|
||||
<%= stock.ticker.upcase %>
|
||||
</div>
|
||||
<div class="flex items-center justify-center gap-1 text-xs <%= stock.percentage_change >= 0 ? 'text-green-up' : 'text-destructive' %>">
|
||||
<% if stock.percentage_change >= 0 %>
|
||||
<%= lucide_icon("trending-up", class: "w-3 h-3") %>
|
||||
<% else %>
|
||||
<%= lucide_icon("trending-down", class: "w-3 h-3") %>
|
||||
<% end %>
|
||||
<%= stock.percentage_change_formatted %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="flex-shrink-0 overflow-hidden" style="width: 400px;">
|
||||
<div class="ticker-container">
|
||||
<div class="ticker-content animate-scroll">
|
||||
<%# Duplicate stocks for seamless infinite scroll: animation moves from 0% to -50%,
|
||||
so when it reaches -50% (showing second copy), it loops back to 0% (first copy)
|
||||
creating smooth continuous scrolling without gaps or visible restarts %>
|
||||
<% (ticker_stocks.to_a * 2).each do |stock| %>
|
||||
<%= render 'layouts/stock_item', stock: stock %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="trix-content">
|
||||
<%= yield -%>
|
||||
</div>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Admin - StocksInTheFuture</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
<%= stylesheet_link_tag :app, 'data-turbo-track': "reload" %>
|
||||
<%= stylesheet_link_tag "actiontext", 'data-turbo-track': "reload" %>
|
||||
<%= javascript_importmap_tags %>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body class="min-h-screen bg-gray-50" data-controller="admin-sidebar">
|
||||
<!-- Admin Header -->
|
||||
<div class="fixed top-0 left-0 w-full z-50 bg-white border-b border-gray-200">
|
||||
<div class="flex items-center justify-between py-3 px-6 h-16">
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
data-action="click->admin-sidebar#toggle"
|
||||
class="lg:hidden inline-flex items-center justify-center w-10 h-10 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
aria-label="Toggle menu">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
<%= link_to admin_root_path do %>
|
||||
<i class="fas fa-tools text-blue-600 mr-2"></i>
|
||||
Admin Dashboard
|
||||
<% end %>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="hidden sm:inline text-sm text-gray-600">
|
||||
<%= current_user.email %>
|
||||
</span>
|
||||
<%= link_to "View Site", root_path, class: "text-sm text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Sign Out", destroy_user_session_path, data: { turbo_method: :delete }, class: "text-sm text-red-600 hover:text-red-800" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Sidebar Navigation -->
|
||||
<div class="fixed top-16 left-0 h-[calc(100vh-4rem)] w-64 bg-white border-r border-gray-200 overflow-y-auto lg:block hidden" id="admin-sidebar">
|
||||
<nav class="p-4">
|
||||
<%= render "admin/shared/navigation" %>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Sidebar Overlay -->
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden"
|
||||
data-admin-sidebar-target="overlay"
|
||||
data-action="click->admin-sidebar#toggle"
|
||||
style="display: none;"></div>
|
||||
|
||||
<!-- Mobile Sidebar -->
|
||||
<div class="fixed top-16 left-0 h-[calc(100vh-4rem)] w-64 bg-white border-r border-gray-200 overflow-y-auto z-50 transform -translate-x-full transition-transform duration-300 lg:hidden"
|
||||
data-admin-sidebar-target="mobileSidebar">
|
||||
<nav class="p-4">
|
||||
<%= render "admin/shared/navigation" %>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="pt-16 lg:pl-64 min-h-screen">
|
||||
<div class="p-6">
|
||||
<!-- Flash Messages -->
|
||||
<% if notice.present? %>
|
||||
<p class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-lg" id="notice"><%= notice %></p>
|
||||
<% end %>
|
||||
<% if alert.present? %>
|
||||
<p class="mb-4 p-4 bg-red-50 border border-red-200 text-red-800 rounded-lg" id="alert"><%= alert %></p>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>StocksInTheFuture</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
<%= stylesheet_link_tag :app, 'data-turbo-track': "reload" %>
|
||||
<%= stylesheet_link_tag "actiontext", 'data-turbo-track': "reload" %>
|
||||
<%= javascript_importmap_tags %>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body class="min-h-screen bg-[#f7f9f3]">
|
||||
<% if user_signed_in? %>
|
||||
<div class="fixed top-0 left-0 w-full z-50 flex items-center justify-between py-4 px-6 h-16 bg-[#f7f9f3]">
|
||||
<div class="flex items-center gap-4">
|
||||
<label
|
||||
for="mobile-menu-toggle"
|
||||
class="lg:hidden cursor-pointer flex items-center justify-center w-10 h-10 bg-[#00698c] text-white rounded-lg hover:bg-[#004f6b] transition-colors"
|
||||
aria-label="Toggle menu">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</label>
|
||||
<%= image_tag "SITF-Horz-no-tag-logo.svg", class: "h-8 w-auto", alt: "Stocks in The Future" %>
|
||||
</div>
|
||||
<div class="hidden lg:block">
|
||||
<%= render 'layouts/stock_ticker' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed w-full h-px z-40 top-16 bg-[#004f6b]"></div>
|
||||
|
||||
<%= render 'layouts/navbar', stocks: @navbar_stocks %>
|
||||
|
||||
<!-- Main: responsive margins and padding -->
|
||||
<main class="min-h-screen overflow-auto px-4 sm:px-6 ml-0 lg:ml-50 mt-16 pb-6 bg-[#f7f9f3]">
|
||||
<!-- Flash Messages -->
|
||||
<% if notice.present? %>
|
||||
<p class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-lg" id="notice"><%= notice %></p>
|
||||
<% end %>
|
||||
<% if alert.present? %>
|
||||
<p class="mb-4 p-4 bg-red-50 border border-red-200 text-red-800 rounded-lg" id="alert"><%= alert %></p>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
</main>
|
||||
<% else %>
|
||||
<main class="min-h-screen overflow-auto p-6 bg-[#f7f9f3]">
|
||||
<%= yield %>
|
||||
</main>
|
||||
<% end %>
|
||||
|
||||
<!-- Trading Modal -->
|
||||
<%= render 'shared/modal' %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
/* Email styles need to be inline */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
<%= yield %>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user