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