chore: init commit

in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
2026-08-11 14:44:09 -04:00
parent 0012380fd3
commit 392781f7aa
781 changed files with 65944 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>&lt;%= form_with model: @resource, builder: Admin::FormBuilder do |f| %>
&lt;%= f.text_field :name, label: "Name", hint: "Enter the resource name" %>
&lt;%= f.submit_button "Save" %>
&lt;% 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>&lt;%= 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>&lt;%= 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>&lt;%= 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>&lt;%= 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>

View File

@@ -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>&lt;%= 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])
) %&gt;</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>&lt;%= admin_show_attributes(
@resource,
attributes: [:name, :email, :created_at]
) %&gt;</code></pre>
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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]}") %>

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
<p>Hello <%= @resource.email %>!</p>
<p>We're contacting you to notify you that your password has been changed.</p>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
<%= link_to t('.portfolio'), field.path %>

View File

@@ -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" } %>

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
<%= turbo_stream.replace "submit-button", partial: "grade_books/submit_button" %>
<%= turbo_stream.replace "finalize-button", partial: "grade_books/finalize_button" %>

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
<div class="trix-content">
<%= yield -%>
</div>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
<%= yield %>

Some files were not shown because too many files have changed in this diff Show More