chore: init commit
in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
<%= form_with model: @announcement, url: @announcement.persisted? ? admin_announcement_path(@announcement) : admin_announcements_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Announcement Details</h3>
|
||||
|
||||
<%= f.text_field :title,
|
||||
label: "Title",
|
||||
hint: "Enter the announcement title (max 255 characters)",
|
||||
placeholder: "Important Update" %>
|
||||
|
||||
<%= f.rich_text_area :content,
|
||||
label: "Content",
|
||||
hint: "Write the full announcement content" %>
|
||||
|
||||
<%= f.boolean_field :featured,
|
||||
label: "Featured",
|
||||
hint: "Only one announcement can be featured at a time. Featuring this will unfeature the current one." %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @announcement.persisted? ? admin_announcement_path(@announcement) : admin_announcements_path %>
|
||||
<%= f.submit_button @announcement.persisted? ? "Update Announcement" : "Create Announcement" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit Announcement</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @announcement.title %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<%= admin_table(
|
||||
@announcements,
|
||||
columns: [
|
||||
{ attribute: :title, label: "Title", sortable: true },
|
||||
{ attribute: :featured, label: "Featured", sortable: true },
|
||||
{ attribute: :created_at, label: "Created", sortable: true },
|
||||
{ attribute: :updated_at, label: "Updated", sortable: true }
|
||||
],
|
||||
title: "Announcements",
|
||||
actions: link_to(
|
||||
"New Announcement",
|
||||
new_admin_announcement_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
),
|
||||
empty_message: "No announcements found. Create your first announcement to get started."
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New Announcement</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,42 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @announcement.title %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_announcement_path(@announcement),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_announcement_path(@announcement),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this announcement?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@announcement,
|
||||
attributes: %i[id title featured created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Rich Text Content Display -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Content</h3>
|
||||
<div class="prose max-w-none">
|
||||
<%= @announcement.content %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Announcements", admin_announcements_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
<%= form_with model: @classroom, url: @classroom.persisted? ? admin_classroom_path(@classroom) : admin_classrooms_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Classroom Details</h3>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Name",
|
||||
hint: "Enter the classroom name (e.g., Ms. Smith's 5th Grade)",
|
||||
placeholder: "Ms. Smith's 5th Grade" %>
|
||||
|
||||
<%= f.collection_check_boxes :grade_ids, Grade.all, :id, :name,
|
||||
label: "Grades",
|
||||
hint: "Select the grade levels for this classroom" do |b|
|
||||
content_tag :div, class: "flex items-center" do
|
||||
b.check_box(class: "mr-2") + b.label(class: "text-sm")
|
||||
end
|
||||
end %>
|
||||
|
||||
<%= f.select :school_year_id,
|
||||
options_from_collection_for_select(
|
||||
SchoolYear.joins(:school, :year).order("schools.name, years.name"),
|
||||
:id,
|
||||
:name,
|
||||
@classroom.school_year_id
|
||||
),
|
||||
label: "School Year",
|
||||
hint: "Select the school year for this classroom",
|
||||
include_blank: "Select a school year" %>
|
||||
|
||||
<%= f.boolean_field :trading_enabled,
|
||||
label: "Trading Enabled",
|
||||
hint: "Allow students to trade stocks in this classroom" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @classroom.persisted? ? admin_classroom_path(@classroom) : admin_classrooms_path %>
|
||||
<%= f.submit_button @classroom.persisted? ? "Update Classroom" : "Create Classroom" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit Classroom</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @classroom.name %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,106 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Classrooms</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New Classroom", new_admin_classroom_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:name, "Name") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Grade
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
School Year
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:archived, "Archived") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:trading_enabled, "Trading Enabled") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @classrooms.any? %>
|
||||
<% @classrooms.each do |classroom| %>
|
||||
<tr id="<%= dom_id(classroom) %>" class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= admin_classroom_path(classroom) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= classroom.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= classroom.name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= classroom.grades.pluck(:name).join(', ') %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= "#{classroom.school_name} - #{classroom.year_name}" %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<% if classroom.archived? %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<% if classroom.trading_enabled? %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_classroom_path(classroom), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_classroom_path(classroom), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<% if classroom.archived? %>
|
||||
<%= link_to "Activate", toggle_archive_admin_classroom_path(classroom),
|
||||
data: { turbo_method: :patch, turbo_confirm: "Are you sure you want to activate this classroom?" },
|
||||
class: "text-blue-600 hover:text-blue-800" %>
|
||||
<% else %>
|
||||
<%= link_to "Archive", toggle_archive_admin_classroom_path(classroom),
|
||||
data: { turbo_method: :patch, turbo_confirm: "Are you sure you want to archive this classroom? Teachers and students will no longer be able to access it, but all historical data will be preserved." },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="7" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No classrooms found. Create your first classroom to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New Classroom</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,76 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @classroom.name %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_classroom_path(@classroom),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= classroom_archive_toggle_button(@classroom) %>
|
||||
<%= link_to admin_classroom_path(@classroom),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this classroom?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@classroom,
|
||||
attributes: %i[id name grades_display archived trading_enabled school_year created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Students -->
|
||||
<% if @classroom.students.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Students (<%= @classroom.students.count %>)</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @classroom.students.limit(10).each do |student| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= student.username %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if @classroom.students.count > 10 %>
|
||||
<li class="text-sm text-gray-500">
|
||||
... and <%= @classroom.students.count - 10 %> more
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No students in this classroom yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Associated Teachers -->
|
||||
<% if @classroom.teachers.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Teachers</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @classroom.teachers.each do |teacher| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= teacher.username %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No teachers assigned to this classroom yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Classrooms", admin_classrooms_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,282 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Page Header -->
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mt-8 mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">Form Builder Demo</h1>
|
||||
<p class="text-gray-600">Showcase of Admin::FormBuilder with all field types</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Box -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-info-circle text-blue-600 mt-1 mr-3"></i>
|
||||
<div>
|
||||
<h3 class="font-semibold text-blue-900 mb-1">Form Builder Features</h3>
|
||||
<p class="text-sm text-blue-800">
|
||||
This form builder provides consistent Tailwind styling, automatic error display,
|
||||
and support for all common field types including associations, currency fields, and rich text.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Demo -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-6">Sample Form</h2>
|
||||
|
||||
<%= form_with model: @user, url: "#", builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<!-- Text Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Text Fields</h3>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Full Name",
|
||||
hint: "Enter the user's full name" %>
|
||||
|
||||
<%= f.email_field :email,
|
||||
label: "Email Address",
|
||||
placeholder: "user@example.com" %>
|
||||
|
||||
<%= f.password_field :password,
|
||||
label: "Password",
|
||||
hint: "Minimum 8 characters" %>
|
||||
</div>
|
||||
|
||||
<!-- Number & Currency Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Number & Currency Fields</h3>
|
||||
|
||||
<%= f.number_field :id,
|
||||
label: "User ID",
|
||||
hint: "Auto-generated unique identifier" %>
|
||||
|
||||
<%= f.currency_field :id,
|
||||
label: "Account Balance",
|
||||
multiplier: 0.01,
|
||||
decimals: 2,
|
||||
hint: "Currency field with $ prefix" %>
|
||||
</div>
|
||||
|
||||
<!-- Text Area -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Text Area</h3>
|
||||
|
||||
<%= f.text_area :email,
|
||||
label: "Biography",
|
||||
rows: 4,
|
||||
hint: "Tell us about yourself" %>
|
||||
</div>
|
||||
|
||||
<!-- Select Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Select Fields</h3>
|
||||
|
||||
<%= f.select_field :type,
|
||||
label: "User Type",
|
||||
collection: [%w[Student Student], %w[Teacher Teacher], %w[User User]],
|
||||
include_blank: "Select a type..." %>
|
||||
</div>
|
||||
|
||||
<!-- Boolean Field -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Boolean Field</h3>
|
||||
|
||||
<%= f.boolean_field :admin,
|
||||
label: "Administrator",
|
||||
hint: "Grant admin privileges to this user" %>
|
||||
</div>
|
||||
|
||||
<!-- Date & DateTime Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Date & DateTime Fields</h3>
|
||||
|
||||
<%= f.date_field :created_at,
|
||||
label: "Registration Date" %>
|
||||
|
||||
<%= f.datetime_field :updated_at,
|
||||
label: "Last Updated" %>
|
||||
</div>
|
||||
|
||||
<!-- Read-Only Fields -->
|
||||
<div class="border-b border-gray-200 pb-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Read-Only Fields</h3>
|
||||
|
||||
<%= f.read_only_field :id,
|
||||
label: "User ID (Read-Only)" %>
|
||||
|
||||
<%= f.read_only_field :created_at,
|
||||
label: "Member Since" %>
|
||||
|
||||
<%= f.read_only_field :admin,
|
||||
label: "Administrator Status" %>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center justify-end gap-x-4 pt-4">
|
||||
<%= f.cancel_button "Cancel", url: admin_component_demo_index_path %>
|
||||
<%= f.submit_button "Save Changes" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validation Error Demo -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Validation Error Display</h2>
|
||||
<p class="text-gray-600 mb-4">
|
||||
The form builder automatically displays validation errors inline with fields.
|
||||
</p>
|
||||
|
||||
<%= link_to "Show Form with Errors",
|
||||
form_admin_component_demo_index_path(show_errors: true),
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Examples -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Usage Examples</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Basic Form Setup</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= form_with model: @resource, builder: Admin::FormBuilder do |f| %>
|
||||
<%= f.text_field :name, label: "Name", hint: "Enter the resource name" %>
|
||||
<%= f.submit_button "Save" %>
|
||||
<% end %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Currency Field</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.currency_field :amount_cents,
|
||||
multiplier: 0.01,
|
||||
decimals: 2,
|
||||
label: "Amount" %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Association Select</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.association_select :classroom_id,
|
||||
collection: Classroom.all,
|
||||
label_method: :name,
|
||||
value_method: :id,
|
||||
include_blank: "Select a classroom..." %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Boolean Field</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.boolean_field :active,
|
||||
label: "Active",
|
||||
hint: "Check to activate this resource" %></code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Read-Only Field</h3>
|
||||
<pre class="text-xs bg-gray-50 p-4 rounded border border-gray-200 overflow-x-auto"><code><%= f.read_only_field :created_at,
|
||||
label: "Created At",
|
||||
value: @resource.created_at.strftime("%B %d, %Y") %></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Field Reference -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-8">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Available Field Types</h2>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">Method</th>
|
||||
<th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Description</th>
|
||||
<th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">text_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Standard text input</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, placeholder</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">email_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Email input with validation</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, placeholder</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">password_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Password input (masked)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">number_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Numeric input</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, step</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">currency_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Currency with $ prefix</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">multiplier, decimals, label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">text_area</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Multi-line text input</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint, rows</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">rich_text_area</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Rich text editor (Trix)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">select_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Dropdown select</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">collection, include_blank, label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">boolean_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Checkbox with label</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">date_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Date picker</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">datetime_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Date and time picker</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">association_select</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Association dropdown</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">collection, label_method, value_method, include_blank</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">read_only_field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Display-only field</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">label, value, hint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">submit_button</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Submit button (blue)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-4 pl-4 pr-3 text-sm font-mono text-gray-900">cancel_button</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">Cancel link (styled as button)</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700">text, url</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,193 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Page Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">Admin Components Demo</h1>
|
||||
<p class="text-gray-600">Showcase of all reusable admin components</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Box -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-info-circle text-blue-600 mt-1 mr-3"></i>
|
||||
<div>
|
||||
<h3 class="font-semibold text-blue-900 mb-1">Component Demo Page</h3>
|
||||
<p class="text-sm text-blue-800">
|
||||
This page demonstrates all the reusable components created for the Admin V2 interface.
|
||||
These components will be used across all admin resource pages.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg p-4 mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Component Demos</h3>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to form_admin_component_demo_index_path, class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" do %>
|
||||
<i class="fas fa-edit mr-2"></i>
|
||||
Form Builder Demo
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumbs Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Breadcrumbs Component</h2>
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Search/Filter Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Search & Filter Component</h2>
|
||||
<%= admin_search_filter(
|
||||
filters: @filters,
|
||||
search_placeholder: "Search users..."
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Table Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Table Component</h2>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Responsive table with sortable columns and empty states.
|
||||
Click column headers to sort. (Action buttons disabled in demo - see Action Buttons section below)
|
||||
</p>
|
||||
|
||||
<%= admin_table(
|
||||
@users,
|
||||
columns: @columns,
|
||||
title: "Users",
|
||||
actions: link_to("New User", "#", class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"),
|
||||
empty_message: "No users found. Create your first user to get started.",
|
||||
show_actions: false
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Show Attributes Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Show Attributes Component</h2>
|
||||
<p class="text-gray-600 mb-4">Clean attribute display for show/detail pages.</p>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@sample_user,
|
||||
attributes: %i[id email name type admin created_at updated_at]
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Action Buttons Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Action Buttons Component</h2>
|
||||
<p class="text-gray-600 mb-4">Standard and custom action buttons with icons.</p>
|
||||
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm ring-1 ring-gray-900/5">
|
||||
<h3 class="text-sm font-medium text-gray-900 mb-4">Standard Actions (Edit + Delete)</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<%= link_to "#", class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to "#", data: { turbo_method: :delete, turbo_confirm: "Are you sure?" }, class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<hr class="my-6">
|
||||
|
||||
<h3 class="text-sm font-medium text-gray-900 mb-4">With Custom Actions</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<%= link_to "#", class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to "#", class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-blue-700 bg-blue-50 hover:bg-blue-100 border-blue-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-envelope -ml-1 mr-2 h-5 w-5"></i>
|
||||
Send Email
|
||||
<% end %>
|
||||
<%= link_to "#", data: { turbo_confirm: "Archive this user?" }, class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-archive -ml-1 mr-2 h-5 w-5"></i>
|
||||
Archive
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Boolean Badge Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Boolean Badges</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm ring-1 ring-gray-900/5">
|
||||
<div class="flex gap-4 items-center">
|
||||
<div>
|
||||
<span class="text-sm text-gray-600 mr-2">True:</span>
|
||||
<%= boolean_badge(true) %>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-600 mr-2">False:</span>
|
||||
<%= boolean_badge(false) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Format Attribute Demo -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Attribute Formatting Examples</h2>
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm ring-1 ring-gray-900/5">
|
||||
<dl class="space-y-4">
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">String:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(@sample_user, :email) %></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">Boolean:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(@sample_user, :admin) %></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">Date:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(@sample_user, :created_at) %></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm font-medium text-gray-500">Nil Value:</dt>
|
||||
<dd class="text-sm text-gray-900"><%= format_attribute(User.new, :name) %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-8">
|
||||
|
||||
<!-- Component Usage Examples -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Usage Examples</h2>
|
||||
<div class="bg-gray-50 p-6 rounded-lg">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">Table Component</h3>
|
||||
<pre class="text-xs bg-white p-4 rounded border border-gray-200 overflow-x-auto"><code><%= admin_table(
|
||||
@resources,
|
||||
columns: [
|
||||
{ attribute: :name, label: "Name", sortable: true, link: true },
|
||||
{ attribute: :status, label: "Status", sortable: true }
|
||||
],
|
||||
title: "Resources",
|
||||
actions: link_to("New", [:new, :admin, :resource])
|
||||
) %></code></pre>
|
||||
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3 mt-6">Show Attributes</h3>
|
||||
<pre class="text-xs bg-white p-4 rounded border border-gray-200 overflow-x-auto"><code><%= admin_show_attributes(
|
||||
@resource,
|
||||
attributes: [:name, :email, :created_at]
|
||||
) %></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">User Details</h1>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@user,
|
||||
attributes: %i[id email name type admin classroom_id created_at updated_at]
|
||||
) %>
|
||||
|
||||
<div class="mt-6">
|
||||
<%= admin_actions(
|
||||
@user,
|
||||
actions: %i[edit delete],
|
||||
custom_actions: [
|
||||
{
|
||||
label: "Back to List",
|
||||
path: admin_component_demo_index_path,
|
||||
icon: "fas fa-arrow-left",
|
||||
class: "text-gray-700 bg-white hover:bg-gray-50 border-gray-300"
|
||||
}
|
||||
]
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,102 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Admin Dashboard</h1>
|
||||
<p class="text-gray-600 mt-2">Welcome to the in-house admin interface</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<!-- Academic Resources -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-graduation-cap text-blue-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Academic</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_classrooms_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-chalkboard w-4 mr-2"></i>Classrooms
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_schools_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-school w-4 mr-2"></i>Schools
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_school_years_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-calendar-check w-4 mr-2"></i>School Years
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- User Management -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-users text-green-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">User Management</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_students_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-user-graduate w-4 mr-2"></i>Students
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_teachers_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-chalkboard-teacher w-4 mr-2"></i>Teachers
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to admin_users_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-users w-4 mr-2"></i>Users
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Portfolio & Finance -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-chart-line text-purple-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Portfolio & Finance</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_stocks_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-chart-line w-4 mr-2"></i>Stocks
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="text-gray-600"><i class="fas fa-exchange-alt w-4 mr-2"></i>Portfolio Transactions</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||
<div class="flex items-center mb-4">
|
||||
<i class="fas fa-bullhorn text-orange-600 text-2xl mr-3"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Content</h2>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li>
|
||||
<%= link_to admin_announcements_path, class: "text-blue-600 hover:text-blue-800 hover:underline" do %>
|
||||
<i class="fas fa-bullhorn w-4 mr-2"></i>Announcements
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-600 mt-1 mr-3"></i>
|
||||
<div>
|
||||
<h3 class="font-semibold text-yellow-900 mb-1">Development Status</h3>
|
||||
<p class="text-sm text-yellow-800">
|
||||
Resource pages are being built incrementally. Use the sidebar navigation to access completed resources.
|
||||
Links marked with "#" are not yet implemented.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
<%= form_with model: portfolio_transaction, url: portfolio_transaction.persisted? ? admin_portfolio_transaction_path(portfolio_transaction) : admin_portfolio_transactions_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Transaction Details</h3>
|
||||
|
||||
<%= f.association_select :portfolio_id,
|
||||
collection: Portfolio.includes(:user).order("users.username"),
|
||||
label: "Portfolio",
|
||||
label_method: ->(portfolio) { "#{portfolio.user.username} (ID: #{portfolio.id})" },
|
||||
hint: "Select the portfolio for this transaction" %>
|
||||
|
||||
<%= f.select :transaction_type,
|
||||
PortfolioTransaction.transaction_types.keys.map { |type| [type.humanize, type] },
|
||||
label: "Transaction Type",
|
||||
hint: "Type of transaction (deposit, withdrawal, credit, debit, fee)",
|
||||
include_blank: "Select transaction type..." %>
|
||||
|
||||
<%= f.select :reason,
|
||||
PortfolioTransaction.reasons.keys.map { |r| [r.humanize, r] },
|
||||
label: "Reason",
|
||||
hint: "Reason for this transaction (optional)",
|
||||
include_blank: "Select reason..." %>
|
||||
|
||||
<%= f.currency_field :amount_cents,
|
||||
label: "Amount",
|
||||
hint: "Transaction amount in dollars",
|
||||
multiplier: 0.01,
|
||||
decimals: 2 %>
|
||||
|
||||
<%= f.text_area :description,
|
||||
label: "Description",
|
||||
hint: "Optional description for this transaction",
|
||||
rows: 4 %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: portfolio_transaction.persisted? ? admin_portfolio_transaction_path(portfolio_transaction) : admin_root_path %>
|
||||
<%= f.submit_button portfolio_transaction.persisted? ? "Update Portfolio Transaction" : "Create Portfolio Transaction" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">Edit Portfolio Transaction #<%= @portfolio_transaction.id %></h2>
|
||||
|
||||
<%= render "form", portfolio_transaction: @portfolio_transaction %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">New Portfolio Transaction</h2>
|
||||
|
||||
<%= render "form", portfolio_transaction: @portfolio_transaction %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900">Portfolio Transaction #<%= @portfolio_transaction.id %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_portfolio_transaction_path(@portfolio_transaction),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_portfolio_transaction_path(@portfolio_transaction),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this portfolio transaction?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@portfolio_transaction,
|
||||
attributes: %i[id created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Portfolio Information -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio</h3>
|
||||
<dl class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Portfolio ID</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<%= @portfolio_transaction.portfolio_id %>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">User</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @portfolio_transaction.portfolio.user.username %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Transaction Details -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Transaction Details</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Transaction Type</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<%= @portfolio_transaction.transaction_type.humanize %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Amount</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 font-semibold"><%= number_to_currency(@portfolio_transaction.amount_cents / 100.0) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Reason</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @portfolio_transaction.reason&.humanize || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<% if @portfolio_transaction.description.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Description</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @portfolio_transaction.description %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Associated Order -->
|
||||
<% if @portfolio_transaction.order.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Associated Order</h3>
|
||||
<p class="text-sm text-gray-900">Order #<%= @portfolio_transaction.order.id %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back", :back,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,45 @@
|
||||
<%= form_with model: @school_year, url: @school_year.persisted? ? admin_school_year_path(@school_year) : admin_school_years_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">School Year Details</h3>
|
||||
|
||||
<%= f.base_errors %>
|
||||
|
||||
<%= f.select :school_id,
|
||||
options_from_collection_for_select(
|
||||
School.order(:name),
|
||||
:id,
|
||||
:name,
|
||||
@school_year.school_id
|
||||
),
|
||||
label: "School",
|
||||
hint: "Select the school for this school year",
|
||||
include_blank: "Select a school" %>
|
||||
|
||||
<%= f.select :year_id,
|
||||
options_from_collection_for_select(
|
||||
Year.ordered_by_start_year,
|
||||
:id,
|
||||
:name,
|
||||
@school_year.year_id
|
||||
),
|
||||
label: "Year",
|
||||
hint: "Select the year for this school year",
|
||||
include_blank: "Select a year" %>
|
||||
|
||||
<% if @school_year.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-600">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Four quarters will be automatically created for this school year.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @school_year.persisted? ? admin_school_year_path(@school_year) : admin_school_years_path %>
|
||||
<%= f.submit_button @school_year.persisted? ? "Update School Year" : "Create School Year" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit School Year</h1>
|
||||
|
||||
<%= render "form", school_year: @school_year %>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900">School Years</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New School Year", new_admin_school_year_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
School
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Year
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:created_at, "Created At") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @school_years.any? %>
|
||||
<% @school_years.each do |school_year| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= admin_school_year_path(school_year) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.school_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.year_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= school_year.created_at.strftime("%B %d, %Y") %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_school_year_path(school_year), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_school_year_path(school_year), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<%= link_to "Delete", admin_school_year_path(school_year),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="5" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No school years found. Create your first school year to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New School Year</h1>
|
||||
|
||||
<%= render "form", school_year: @school_year %>
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= "#{@school_year.school_name} (#{@school_year.year_name})" %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_school_year_path(@school_year),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_school_year_path(@school_year),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this school year?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@school_year,
|
||||
attributes: %i[id school_name year_name created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Classrooms -->
|
||||
<% if @school_year.classrooms.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Classrooms (<%= @school_year.classrooms.count %>)</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @school_year.classrooms.limit(10).each do |classroom| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= classroom.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if @school_year.classrooms.count > 10 %>
|
||||
<li class="text-sm text-gray-500">
|
||||
... and <%= @school_year.classrooms.count - 10 %> more
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No classrooms associated with this school year yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Associated Quarters -->
|
||||
<% if @school_year.quarters.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Quarters</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @school_year.quarters.order(:number).each do |quarter| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= quarter.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to School Years", admin_school_years_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<%= form_with model: @school, url: @school.persisted? ? admin_school_path(@school) : admin_schools_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">School Details</h3>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Name",
|
||||
hint: "Enter the school name",
|
||||
placeholder: "Lincoln Elementary School" %>
|
||||
|
||||
<%= f.collection_check_boxes :year_ids, @years, :id, :name,
|
||||
label: "Years",
|
||||
hint: "Select the school years this school will be active" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @school.persisted? ? admin_school_path(@school) : admin_schools_path %>
|
||||
<%= f.submit_button @school.persisted? ? "Update School" : "Create School" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit School</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @school.name %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<%= admin_table(
|
||||
@schools,
|
||||
columns: [
|
||||
{ attribute: :id, label: "ID", sortable: true },
|
||||
{ attribute: :name, label: "Name", sortable: true },
|
||||
{ attribute: :created_at, label: "Created", sortable: true }
|
||||
],
|
||||
title: "Schools",
|
||||
actions: link_to(
|
||||
"New School",
|
||||
new_admin_school_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
),
|
||||
empty_message: "No schools found. Create your first school to get started."
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New School</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @school.name %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_school_path(@school),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_school_path(@school),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this school?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@school,
|
||||
attributes: %i[id name created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Years -->
|
||||
<% if @school.years.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Associated Years</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @school.years.each do |year| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= year.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No years associated with this school yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Schools", admin_schools_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<% if actions.include?(:edit) %>
|
||||
<%= link_to [:edit, :admin, resource], class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if actions.include?(:delete) %>
|
||||
<%= link_to [:admin, resource],
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this #{resource.class.name.downcase}?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% custom_actions.each do |action| %>
|
||||
<%= link_to action[:path],
|
||||
method: action[:method] || :get,
|
||||
data: action[:data] || {},
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md #{action[:class] || 'text-gray-700 bg-white hover:bg-gray-50'} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
|
||||
<% if action[:icon] %>
|
||||
<i class="<%= action[:icon] %> -ml-1 mr-2 h-5 w-5"></i>
|
||||
<% end %>
|
||||
<%= action[:label] %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<nav class="flex mb-6" aria-label="Breadcrumb">
|
||||
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
||||
<li class="inline-flex items-center">
|
||||
<%= link_to admin_root_path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" do %>
|
||||
<i class="fas fa-home w-4 h-4 mr-2"></i>
|
||||
Dashboard
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<% breadcrumbs.each_with_index do |crumb, index| %>
|
||||
<li>
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-chevron-right w-3 h-3 text-gray-400 mx-1"></i>
|
||||
<% if index == breadcrumbs.length - 1 %>
|
||||
<span class="ml-1 text-sm font-medium text-gray-500 md:ml-2">
|
||||
<%= crumb[:label] %>
|
||||
</span>
|
||||
<% else %>
|
||||
<%= link_to crumb[:label], crumb[:path], class: "ml-1 text-sm font-medium text-gray-700 hover:text-blue-600 md:ml-2" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</nav>
|
||||
@@ -0,0 +1,92 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Dashboard -->
|
||||
<div>
|
||||
<%= link_to root_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-home w-5 mr-3 text-gray-500"></i>
|
||||
Dashboard
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Academic Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Academic
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_classrooms_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-chalkboard w-5 mr-3 text-gray-500"></i>
|
||||
Classrooms
|
||||
<% end %>
|
||||
<%= link_to admin_schools_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-school w-5 mr-3 text-gray-500"></i>
|
||||
Schools
|
||||
<% end %>
|
||||
<%= link_to admin_school_years_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-calendar-check w-5 mr-3 text-gray-500"></i>
|
||||
School Years
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Users
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_students_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-user-graduate w-5 mr-3 text-gray-500"></i>
|
||||
Students
|
||||
<% end %>
|
||||
<%= link_to admin_teachers_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-chalkboard-teacher w-5 mr-3 text-gray-500"></i>
|
||||
Teachers
|
||||
<% end %>
|
||||
<%= link_to admin_users_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-users w-5 mr-3 text-gray-500"></i>
|
||||
Users
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Portfolio Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Portfolio
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_stocks_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-chart-line w-5 mr-3 text-gray-500"></i>
|
||||
Stocks
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Section -->
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Content
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_announcements_path, class: "flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-bullhorn w-5 mr-3 text-gray-500"></i>
|
||||
Announcements
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Development Section (remove in production) -->
|
||||
<% if Rails.env.development? %>
|
||||
<div>
|
||||
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
|
||||
Development
|
||||
</h3>
|
||||
<div class="space-y-1">
|
||||
<%= link_to admin_component_demo_index_path, class: "flex items-center px-3 py-2 text-sm font-medium text-purple-700 hover:bg-purple-50 hover:text-purple-900 rounded-lg transition-colors" do %>
|
||||
<i class="fas fa-flask w-5 mr-3 text-purple-500"></i>
|
||||
Component Demo
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
<% if collection.respond_to?(:total_pages) && collection.total_pages > 1 %>
|
||||
<nav class="flex items-center justify-between" aria-label="Pagination">
|
||||
<div class="hidden sm:block">
|
||||
<p class="text-sm text-gray-700">
|
||||
Showing
|
||||
<span class="font-medium"><%= collection.offset_value + 1 %></span>
|
||||
to
|
||||
<span class="font-medium"><%= [collection.offset_value + collection.limit_value, collection.total_count].min %></span>
|
||||
of
|
||||
<span class="font-medium"><%= collection.total_count %></span>
|
||||
results
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-1 justify-between sm:justify-end">
|
||||
<% if collection.prev_page %>
|
||||
<%= link_to "Previous", request.params.merge(page: collection.prev_page), class: "relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<% else %>
|
||||
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-400 bg-gray-100 cursor-not-allowed">
|
||||
Previous
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% if collection.next_page %>
|
||||
<%= link_to "Next", request.params.merge(page: collection.next_page), class: "ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<% else %>
|
||||
<span class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-400 bg-gray-100 cursor-not-allowed">
|
||||
Next
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</nav>
|
||||
<% end %>
|
||||
@@ -0,0 +1,38 @@
|
||||
<% filters ||= [] %>
|
||||
<% search_placeholder ||= "Search..." %>
|
||||
<div class="bg-white p-4 shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<%= form_with url: request.path, method: :get, class: "space-y-4 sm:space-y-0 sm:flex sm:items-center sm:gap-4" do |form| %>
|
||||
<!-- Search Field -->
|
||||
<div class="flex-1">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<div class="relative">
|
||||
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-5">
|
||||
<i class="fas fa-search h-4 w-4 text-gray-400"></i>
|
||||
</div>
|
||||
<%= form.text_field :search,
|
||||
value: params[:search],
|
||||
placeholder: search_placeholder,
|
||||
class: "block w-full rounded-md border-0 py-2 pl-14 pr-3 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Dropdowns -->
|
||||
<% filters.each do |filter| %>
|
||||
<div class="sm:w-48">
|
||||
<label for="<%= filter[:name] %>" class="sr-only"><%= filter[:label] %></label>
|
||||
<%= form.select filter[:name],
|
||||
options_for_select(filter[:options], params[filter[:name]]),
|
||||
{ include_blank: filter[:label] },
|
||||
class: "block w-full rounded-md border-0 py-2 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex gap-2">
|
||||
<%= form.submit "Filter", class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
||||
<% if params[:search].present? || filters.any? { |f| params[f[:name]].present? } %>
|
||||
<%= link_to "Clear", request.path, class: "inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<dl class="divide-y divide-gray-200">
|
||||
<% attributes.each do |attribute| %>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4" data-testid="<%= attribute %>">
|
||||
<dt class="text-sm font-medium text-gray-500">
|
||||
<%= attribute.to_s.humanize %>
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
||||
<%= format_attribute(resource, attribute) %>
|
||||
</dd>
|
||||
</div>
|
||||
<% end %>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,86 @@
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<% if local_assigns[:title] || local_assigns[:actions] %>
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<% if local_assigns[:title] %>
|
||||
<h3 class="text-lg font-semibold text-gray-900"><%= title %></h3>
|
||||
<% end %>
|
||||
<% if local_assigns[:actions] %>
|
||||
<div class="flex gap-2">
|
||||
<%= actions %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<% columns.each do |column| %>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<% if column[:sortable] %>
|
||||
<%= sort_link(column[:attribute], column[:label]) %>
|
||||
<% else %>
|
||||
<%= column[:label] %>
|
||||
<% end %>
|
||||
</th>
|
||||
<% end %>
|
||||
<% if options[:show_actions] != false %>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if collection.any? %>
|
||||
<% collection.each do |record| %>
|
||||
<tr id="<%= dom_id(record) %>" class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= url_for([:admin, route_model(record)]) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<% columns.each do |column| %>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900" data-testid="<%= column[:attribute] %>">
|
||||
<% if column[:link] %>
|
||||
<%= link_to format_attribute(record, column[:attribute]), [:admin, route_model(record)], class: "text-blue-600 hover:text-blue-800 font-medium" %>
|
||||
<% else %>
|
||||
<%= format_attribute(record, column[:attribute]) %>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
<% if options[:show_actions] != false %>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", [:admin, route_model(record)], class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", [:edit, :admin, route_model(record)], class: "text-gray-600 hover:text-gray-800" %>
|
||||
<%= link_to "Delete", [:admin, route_model(record)],
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
</div>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="<%= columns.length + (options[:show_actions] == false ? 0 : 1) %>" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<% if options[:empty_message] %>
|
||||
<p class="text-gray-400 mt-1"><%= options[:empty_message] %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<% if collection.respond_to?(:total_pages) && collection.total_pages > 1 %>
|
||||
<div class="px-4 py-3 border-t border-gray-200 sm:px-6">
|
||||
<%= render "admin/shared/pagination", collection: collection %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,126 @@
|
||||
<%= form_with model: @stock, url: @stock.persisted? ? admin_stock_path(@stock) : admin_stocks_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Basic Information</h3>
|
||||
|
||||
<%= f.text_field :ticker,
|
||||
label: "Ticker Symbol",
|
||||
hint: "Stock ticker symbol (e.g., AAPL, GOOGL)",
|
||||
placeholder: "AAPL" %>
|
||||
|
||||
<%= f.text_field :company_name,
|
||||
label: "Company Name",
|
||||
hint: "Full company name",
|
||||
placeholder: "Apple Inc." %>
|
||||
|
||||
<%= f.text_field :company_website,
|
||||
label: "Company Website",
|
||||
hint: "Company website URL (must be HTTP or HTTPS)",
|
||||
placeholder: "https://www.apple.com" %>
|
||||
|
||||
<%= f.text_field :stock_exchange,
|
||||
label: "Stock Exchange",
|
||||
hint: "Exchange where stock is traded (e.g., NASDAQ, NYSE)",
|
||||
placeholder: "NASDAQ" %>
|
||||
|
||||
<%= f.text_area :description,
|
||||
label: "Description",
|
||||
hint: "Company description and overview",
|
||||
rows: 4 %>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Pricing</h4>
|
||||
|
||||
<%= f.currency_field :price_cents,
|
||||
label: "Current Price",
|
||||
hint: "Current stock price in dollars",
|
||||
multiplier: 0.01,
|
||||
decimals: 2 %>
|
||||
|
||||
<%= f.currency_field :yesterday_price_cents,
|
||||
label: "Yesterday's Price",
|
||||
hint: "Previous day's closing price in dollars",
|
||||
multiplier: 0.01,
|
||||
decimals: 2 %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Company Details</h4>
|
||||
|
||||
<%= f.number_field :employees,
|
||||
label: "Number of Employees",
|
||||
hint: "Total number of employees" %>
|
||||
|
||||
<%= f.text_field :industry,
|
||||
label: "Industry",
|
||||
hint: "Industry sector",
|
||||
placeholder: "Technology" %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Financial Metrics</h4>
|
||||
|
||||
<%= f.text_field :cash_flow,
|
||||
label: "Cash Flow",
|
||||
hint: "Company cash flow" %>
|
||||
|
||||
<%= f.text_field :debt,
|
||||
label: "Debt",
|
||||
hint: "Total company debt" %>
|
||||
|
||||
<%= f.text_field :debt_to_equity,
|
||||
label: "Debt to Equity Ratio",
|
||||
hint: "Debt to equity ratio" %>
|
||||
|
||||
<%= f.text_field :profit_margin,
|
||||
label: "Profit Margin",
|
||||
hint: "Company profit margin" %>
|
||||
|
||||
<%= f.text_field :sales_growth,
|
||||
label: "Sales Growth",
|
||||
hint: "Sales growth percentage" %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Industry Averages</h4>
|
||||
|
||||
<%= f.text_field :industry_avg_debt_to_equity,
|
||||
label: "Industry Avg Debt to Equity",
|
||||
hint: "Industry average debt to equity ratio" %>
|
||||
|
||||
<%= f.text_field :industry_avg_profit_margin,
|
||||
label: "Industry Avg Profit Margin",
|
||||
hint: "Industry average profit margin" %>
|
||||
|
||||
<%= f.text_field :industry_avg_sales_growth,
|
||||
label: "Industry Avg Sales Growth",
|
||||
hint: "Industry average sales growth" %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Additional Information</h4>
|
||||
|
||||
<%= f.text_area :management,
|
||||
label: "Management",
|
||||
hint: "Information about company management",
|
||||
rows: 4 %>
|
||||
|
||||
<%= f.text_area :competitor_names,
|
||||
label: "Competitor Names",
|
||||
hint: "List of competitor companies",
|
||||
rows: 3 %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<%= f.boolean_field :archived,
|
||||
label: "Archived",
|
||||
hint: "Mark this stock as archived (hidden from active listings)" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @stock.persisted? ? admin_stock_path(@stock) : admin_stocks_path %>
|
||||
<%= f.submit_button @stock.persisted? ? "Update Stock" : "Create Stock" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit Stock</h1>
|
||||
|
||||
<%= render "form", stock: @stock %>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<%= admin_table(
|
||||
@stocks,
|
||||
columns: [
|
||||
{ attribute: :id, label: "ID", sortable: true },
|
||||
{ attribute: :ticker, label: "Ticker", sortable: true },
|
||||
{ attribute: :company_name, label: "Company Name", sortable: true },
|
||||
{ attribute: :company_website, label: "Website", sortable: false },
|
||||
{ attribute: :current_price, label: "Current Price", sortable: false },
|
||||
{ attribute: :archived, label: "Archived", sortable: true }
|
||||
],
|
||||
title: "Stocks",
|
||||
actions: link_to(
|
||||
"New Stock",
|
||||
new_admin_stock_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||
),
|
||||
empty_message: "No stocks found. Create your first stock to get started."
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New Stock</h1>
|
||||
|
||||
<%= render "form", stock: @stock %>
|
||||
</div>
|
||||
@@ -0,0 +1,150 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @stock.ticker %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_stock_path(@stock),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_stock_path(@stock),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this stock?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@stock,
|
||||
attributes: %i[id ticker company_name company_website stock_exchange archived created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Description -->
|
||||
<% if @stock.description.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Description</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @stock.description %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Price Information -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Price Information</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Current Price</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= number_to_currency(@stock.current_price) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Yesterday's Price</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= number_to_currency(@stock.yesterday_price) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Percentage Change</dt>
|
||||
<dd class="mt-1 text-sm">
|
||||
<span class="<%= @stock.percentage_change.positive? ? 'text-green-600' : 'text-red-600' %>">
|
||||
<%= @stock.percentage_change_formatted %>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Company Information -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Company Information</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Employees</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.employees || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Industry</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Financial Metrics -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Financial Metrics</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Cash Flow</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.cash_flow || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Debt</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.debt || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Debt to Equity</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.debt_to_equity || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Profit Margin</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.profit_margin || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Sales Growth</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.sales_growth || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Industry Averages -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Industry Averages</h3>
|
||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Avg Debt to Equity</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry_avg_debt_to_equity || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Avg Profit Margin</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry_avg_profit_margin || "—" %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Avg Sales Growth</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900"><%= @stock.industry_avg_sales_growth || "—" %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Management -->
|
||||
<% if @stock.management.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Management</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @stock.management %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Competitors -->
|
||||
<% if @stock.competitor_names.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Competitor Names</h3>
|
||||
<p class="text-sm text-gray-900 whitespace-pre-wrap"><%= @stock.competitor_names %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Portfolio Stocks -->
|
||||
<% if @stock.portfolio_stocks.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio Stocks (<%= @stock.portfolio_stocks.count %>)</h3>
|
||||
<p class="text-sm text-gray-500">This stock is held in <%= @stock.portfolio_stocks.count %> portfolio(s)</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Stocks", admin_stocks_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,91 @@
|
||||
<%= form_with model: @student, url: @student.persisted? ? admin_student_path(@student) : admin_students_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Student Details</h3>
|
||||
|
||||
<%= f.text_field :username,
|
||||
label: "Username",
|
||||
hint: "Unique username for the student",
|
||||
placeholder: "johndoe" %>
|
||||
|
||||
<%= f.select :classroom_id,
|
||||
options_from_collection_for_select(
|
||||
Classroom.order(:name),
|
||||
:id,
|
||||
:name,
|
||||
@student.classroom_id
|
||||
),
|
||||
label: "Classroom",
|
||||
hint: "Classroom assignment (required)",
|
||||
include_blank: "Select a classroom" %>
|
||||
|
||||
<% if @student.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Password</h4>
|
||||
<p class="text-sm text-gray-500 mb-4">Leave blank to generate a random password automatically</p>
|
||||
|
||||
<%= f.password_field :password,
|
||||
label: "Password (Optional)",
|
||||
hint: "Minimum 6 characters, or leave blank to auto-generate",
|
||||
placeholder: "••••••••" %>
|
||||
|
||||
<%= f.password_field :password_confirmation,
|
||||
label: "Password Confirmation",
|
||||
hint: "Re-enter password to confirm",
|
||||
placeholder: "••••••••" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @student.persisted? ? admin_student_path(@student) : admin_students_path %>
|
||||
<%= f.submit_button @student.persisted? ? "Update Student" : "Create Student" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @student.persisted? %>
|
||||
<%= form_with url: add_transaction_admin_student_path(@student), method: :post, builder: Admin::FormBuilder, class: "space-y-6 mt-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Add Portfolio Transaction</h3>
|
||||
|
||||
<!-- Current Cash Balance (Read-only) -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Current Cash Balance</label>
|
||||
<div class="text-lg font-semibold text-gray-900">
|
||||
<%= number_to_currency(@student.portfolio&.cash_balance || 0) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= f.select :transaction_type,
|
||||
[["", ""], ["Deposit", "deposit"], ["Debit", "debit"]],
|
||||
label: "Transaction Type",
|
||||
hint: "Select deposit to add funds or debit to remove funds",
|
||||
include_blank: false %>
|
||||
|
||||
<%= f.number_field :add_fund_amount,
|
||||
label: "Amount",
|
||||
hint: "Transaction amount in dollars",
|
||||
placeholder: "0.00",
|
||||
step: "0.01" %>
|
||||
|
||||
<%= f.select :transaction_reason,
|
||||
PortfolioTransaction.reasons.keys.map { |key| [PortfolioTransaction.human_attribute_name("reason.#{key}"), key] },
|
||||
label: "Reason",
|
||||
hint: "Reason for this transaction",
|
||||
include_blank: "Select a reason" %>
|
||||
|
||||
<%= f.text_area :transaction_description,
|
||||
label: "Description (Optional)",
|
||||
hint: "Additional details about this transaction",
|
||||
placeholder: "Enter transaction details...",
|
||||
rows: 3 %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.submit_button "Add Transaction" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit Student</h1>
|
||||
|
||||
<%= render "form", student: @student %>
|
||||
</div>
|
||||
@@ -0,0 +1,144 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Students</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "Template", template_admin_students_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<%= link_to "Import Students", "#",
|
||||
onclick: "document.getElementById('import-modal').classList.remove('hidden'); return false;",
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
<%= link_to "New Student", new_admin_student_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Tabs -->
|
||||
<% current_filter = current_discard_filter %>
|
||||
<div class="flex gap-1 border-b border-gray-200">
|
||||
<%= link_to "Active",
|
||||
admin_students_path,
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :active ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "Archived",
|
||||
admin_students_path(discarded: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :discarded ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "All",
|
||||
admin_students_path(all: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :all ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:username, "Username") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Classroom
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:created_at, "Created At") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @students.any? %>
|
||||
<% @students.each do |student| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= admin_student_path(student) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.username %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.classroom.name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= student.created_at.strftime("%B %d, %Y") %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_student_path(student), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<% unless student.discarded? %>
|
||||
<%= link_to "Edit", edit_admin_student_path(student), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<% end %>
|
||||
<%= discard_restore_action(student) %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="5" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No students found. Create your first student to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Modal -->
|
||||
<div id="import-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<!-- Background overlay -->
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="document.getElementById('import-modal').classList.add('hidden');"></div>
|
||||
|
||||
<!-- Modal content -->
|
||||
<div class="relative bg-white rounded-lg shadow-xl max-w-lg w-full mx-4 z-50">
|
||||
<div class="px-6 py-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900" id="modal-title">
|
||||
Import Students from CSV
|
||||
</h3>
|
||||
<button type="button" class="text-gray-400 hover:text-gray-500" onclick="document.getElementById('import-modal').classList.add('hidden');">
|
||||
<span class="sr-only">Close</span>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-500 mb-4">
|
||||
Upload a CSV file with columns: <strong>classroom_id, username</strong>
|
||||
</p>
|
||||
|
||||
<%= form_with url: import_admin_students_path, multipart: true, local: true do |form| %>
|
||||
<div class="mb-4">
|
||||
<%= form.file_field :csv_file,
|
||||
accept: ".csv",
|
||||
class: "block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none p-2" %>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3">
|
||||
<button type="button"
|
||||
onclick="document.getElementById('import-modal').classList.add('hidden');"
|
||||
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Cancel
|
||||
</button>
|
||||
<%= form.submit "Import Students",
|
||||
class: "inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New Student</h1>
|
||||
|
||||
<%= render "form", student: @student %>
|
||||
</div>
|
||||
@@ -0,0 +1,210 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @student.username %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_student_path(@student),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_student_path(@student),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to archive this student?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-box-archive -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Archive
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<dl class="divide-y divide-gray-200">
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Id</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.id %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Username</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.username %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Classroom</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.classroom.name %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Created at</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.created_at.strftime("%B %d, %Y") %></dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">Updated at</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"><%= @student.updated_at.strftime("%B %d, %Y") %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Associated Portfolio -->
|
||||
<% if @student.portfolio.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio Details</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-blue-50 p-4 rounded-lg">
|
||||
<div class="text-xs font-medium text-blue-600 mb-1" data-testid="cash_balance_label">
|
||||
Cash Balance
|
||||
</div>
|
||||
<div class="text-2xl font-bold text-blue-900"><%= number_to_currency(@student.portfolio.cash_balance) %></div>
|
||||
</div>
|
||||
<div class="bg-green-50 p-4 rounded-lg">
|
||||
<div class="text-xs font-medium text-green-600 mb-1" data-testid="total_portfolio_worth_label">
|
||||
Total Portfolio Worth
|
||||
</div>
|
||||
<div class="text-2xl font-bold text-green-900"><%= number_to_currency(@student.portfolio.total_portfolio_worth) %></div>
|
||||
</div>
|
||||
<div class="bg-purple-50 p-4 rounded-lg">
|
||||
<div class="text-xs font-medium text-purple-600 mb-1">Total Stocks</div>
|
||||
<div class="text-2xl font-bold text-purple-900"><%= @student.portfolio.portfolio_stocks.sum(:shares).to_i %></div>
|
||||
</div>
|
||||
<%= link_to portfolio_path(@student.portfolio), class: "bg-gray-50 p-4 rounded-lg block hover:bg-gray-100 transition-colors" do %>
|
||||
<div class="text-xs font-medium text-gray-600 mb-1">Portfolio ID</div>
|
||||
<div class="text-2xl font-bold text-gray-900">#<%= @student.portfolio.id %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Earnings Summary -->
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-3">Earnings Summary</h4>
|
||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg mb-6">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Category</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500 uppercase">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">Attendance Earnings</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-gray-900"><%= number_to_currency(@earnings_summary.attendance_earnings_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">Math Earnings</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-gray-900"><%= number_to_currency(@earnings_summary.math_earnings_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">Rewards</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-gray-900"><%= number_to_currency(@earnings_summary.awards_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr class="bg-green-50">
|
||||
<td class="px-3 py-2 text-sm font-semibold text-gray-900">Total Earnings</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-bold text-gray-900"><%= number_to_currency(@earnings_summary.total_earnings_cents / 100.0) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm font-medium text-red-600">Transaction Fees</td>
|
||||
<td class="px-3 py-2 text-sm text-right font-medium text-red-600"><%= number_to_currency(@earnings_summary.transaction_fees_cents / 100.0) %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Transactions -->
|
||||
<% transactions = @student.portfolio.portfolio_transactions.order(created_at: :desc) %>
|
||||
<% if transactions.any? %>
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-3">All Transactions (<%= transactions.count %>)</h4>
|
||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg mb-4">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Reason</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500 uppercase">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% transactions.each do |transaction| %>
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">
|
||||
<%= transaction.created_at.strftime("%m/%d/%Y") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= transaction.transaction_type == 'deposit' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' %>">
|
||||
<%= transaction.transaction_type.capitalize %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-900">
|
||||
<%= transaction.reason&.humanize || 'N/A' %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-right font-medium text-gray-900">
|
||||
<%= number_to_currency(transaction.amount_cents / 100.0) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500 mb-4">No transactions yet.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No portfolio associated with this student.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendance -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Attendance</h3>
|
||||
<% if @attendance_entries.any? %>
|
||||
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Year</th>
|
||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase">Quarter</th>
|
||||
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500 uppercase">Attendance Days</th>
|
||||
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500 uppercase">Perfect Attendance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% @attendance_entries.each do |entry| %>
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">
|
||||
<%= entry.year_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-gray-900">
|
||||
<%= entry.quarter_name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-right text-gray-900">
|
||||
<%= entry.attendance_days_display %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-2 text-sm text-center">
|
||||
<% if entry.perfect_attendance? %>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500">No attendance records found.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Students", admin_students_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
<%= form_with model: @teacher, url: @teacher.persisted? ? admin_teacher_path(@teacher) : admin_teachers_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">Teacher Details</h3>
|
||||
|
||||
<%= f.text_field :email,
|
||||
label: "Email",
|
||||
hint: "Also used as the teacher's login username",
|
||||
placeholder: "teacher@example.com" %>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: "Full Name",
|
||||
hint: "Teacher's full name",
|
||||
placeholder: "John Doe" %>
|
||||
|
||||
<div data-controller="form-filter" data-form-filter-frame-id-value="teacher_classroom_selection">
|
||||
<%= f.select :school_id,
|
||||
options_from_collection_for_select(@schools, :id, :name, @selected_school_id),
|
||||
{ label: "School Filter", hint: "Select a school to filter the classroom list", include_blank: "All Schools" },
|
||||
{ name: "school_id", data: { action: "change->form-filter#change" } } %>
|
||||
</div>
|
||||
|
||||
<%= turbo_frame_tag "teacher_classroom_selection", class: "block mb-6" do %>
|
||||
<% if @classrooms.any? %>
|
||||
<%= f.collection_check_boxes :classroom_ids,
|
||||
@classrooms,
|
||||
:id,
|
||||
:name,
|
||||
label: "Classrooms",
|
||||
hint: "Select one or more classrooms to assign to this teacher",
|
||||
wrapper_class: "mb-0" %>
|
||||
<% else %>
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Classrooms</label>
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-800">No Classrooms Available</h3>
|
||||
<p class="text-sm text-yellow-700 mt-1">
|
||||
No Classrooms associated with this school and active year, please
|
||||
<%= link_to "update classrooms", admin_classrooms_path, class: "font-medium underline text-yellow-700 hover:text-yellow-600" %>
|
||||
so they will show up here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @teacher.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-600">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
A temporary password will be generated and a password reset email will be sent to the teacher's email address.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-x-4">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @teacher.persisted? ? admin_teacher_path(@teacher) : admin_teachers_path %>
|
||||
<%= f.submit_button @teacher.persisted? ? "Update Teacher" : "Create Teacher" %>
|
||||
</div>
|
||||
<% if @teacher.discarded? %>
|
||||
<%= link_to "Permanently Delete",
|
||||
admin_teacher_path(@teacher),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to permanently delete this teacher? This action cannot be undone." },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">Edit Teacher</h1>
|
||||
|
||||
<%= render "form", teacher: @teacher %>
|
||||
</div>
|
||||
@@ -0,0 +1,114 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Teachers</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New Teacher", new_admin_teacher_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Tabs -->
|
||||
<% current_filter = current_discard_filter %>
|
||||
<div class="flex gap-1 border-b border-gray-200">
|
||||
<%= link_to "Active",
|
||||
admin_teachers_path,
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :active ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "Deactivated",
|
||||
admin_teachers_path(discarded: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :discarded ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
<%= link_to "All",
|
||||
admin_teachers_path(all: true),
|
||||
class: "px-4 py-2 text-sm font-medium #{current_filter == :all ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700 hover:border-gray-300'}" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:username, "Username") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:email, "Email") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:name, "Name") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:created_at, "Created At") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @teachers.any? %>
|
||||
<% @teachers.each do |teacher| %>
|
||||
<tr id="<%= dom_id(teacher) %>" class="hover:bg-gray-50 transition-colors">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.username %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.email %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.name %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= teacher.created_at.strftime("%B %d, %Y") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm">
|
||||
<% if teacher.discarded? %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/10">Deactivated</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">Active</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", admin_teacher_path(teacher), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_teacher_path(teacher), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<% if teacher.discarded? %>
|
||||
<%= link_to "Reactivate", admin_teacher_reactivation_path(teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to reactivate this teacher?" },
|
||||
class: "text-green-600 hover:text-green-800" %>
|
||||
<% else %>
|
||||
<%= link_to "Deactivate", admin_teacher_deactivation_path(teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to deactivate this teacher?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="7" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No teachers found. Create your first teacher to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-8">New Teacher</h1>
|
||||
|
||||
<%= render "form", teacher: @teacher %>
|
||||
</div>
|
||||
@@ -0,0 +1,74 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @teacher.username %></h2>
|
||||
<% if @teacher.discarded? %>
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/10">Deactivated</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">Active</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_teacher_path(@teacher),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<% if @teacher.discarded? %>
|
||||
<%= link_to admin_teacher_reactivation_path(@teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to reactivate this teacher?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-green-300 shadow-sm text-sm font-medium rounded-md text-green-700 bg-white hover:bg-green-50" do %>
|
||||
<i class="fas fa-check-circle -ml-1 mr-2 h-5 w-5 text-green-500"></i>
|
||||
Reactivate
|
||||
<% end %>
|
||||
<%= link_to admin_teacher_path(@teacher),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to permanently delete this teacher? This action cannot be undone." },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Permanently Delete
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to admin_teacher_deactivation_path(@teacher),
|
||||
data: { turbo_method: :post, turbo_confirm: "Are you sure you want to deactivate this teacher?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Deactivate
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@teacher,
|
||||
attributes: %i[id username email name created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Classrooms -->
|
||||
<% if @teacher.classrooms.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Classrooms</h3>
|
||||
<ul class="space-y-2">
|
||||
<% @teacher.classrooms.each do |classroom| %>
|
||||
<li class="text-sm text-gray-900">
|
||||
<%= classroom.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No classrooms assigned to this teacher yet.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Teachers", admin_teachers_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,62 @@
|
||||
<%= form_with model: @user, scope: :user, url: @user.persisted? ? admin_user_path(@user) : admin_users_path, builder: Admin::FormBuilder, class: "space-y-6" do |f| %>
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg">
|
||||
<div class="px-6 py-6">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-6">User Details</h3>
|
||||
|
||||
<%= f.text_field :username,
|
||||
label: "Username",
|
||||
hint: "Unique username for the user",
|
||||
placeholder: "johndoe" %>
|
||||
|
||||
<%= f.select :type,
|
||||
options_for_select(
|
||||
%w[User Student Teacher].map { |t| [t, t] },
|
||||
@user.type
|
||||
),
|
||||
label: "User Type",
|
||||
hint: "Select the type of user (Student, Teacher, or generic User)",
|
||||
include_blank: false %>
|
||||
|
||||
<%= f.text_field :email,
|
||||
label: "Email",
|
||||
hint: "Email address (required for Teachers and Admins, optional for Students)",
|
||||
placeholder: "user@example.com" %>
|
||||
|
||||
<%= f.boolean_field :admin,
|
||||
label: "Admin",
|
||||
hint: "Grant admin privileges to this user" %>
|
||||
|
||||
<%= f.select :classroom_id,
|
||||
options_from_collection_for_select(
|
||||
Classroom.order(:name),
|
||||
:id,
|
||||
:name,
|
||||
@user.classroom_id
|
||||
),
|
||||
label: "Classroom",
|
||||
hint: "Optional classroom assignment",
|
||||
include_blank: "No classroom" %>
|
||||
|
||||
<% if @user.new_record? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-4">Password</h4>
|
||||
|
||||
<%= f.password_field :password,
|
||||
label: "Password",
|
||||
hint: "Minimum 6 characters",
|
||||
placeholder: "••••••••" %>
|
||||
|
||||
<%= f.password_field :password_confirmation,
|
||||
label: "Password Confirmation",
|
||||
hint: "Re-enter password to confirm",
|
||||
placeholder: "••••••••" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-x-4">
|
||||
<%= f.cancel_button "Cancel", url: @user.persisted? ? admin_user_path(@user) : admin_users_path %>
|
||||
<%= f.submit_button @user.persisted? ? "Update User" : "Create User" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Edit User</h1>
|
||||
<p class="text-gray-600 mt-2"><%= @user.username %></p>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,95 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Users</h3>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "New User", new_admin_user_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:id, "ID") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:username, "Username") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:type, "Type") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:email, "Email") %>
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Classroom
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
<%= sort_link(:admin, "Admin") %>
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% if @users.any? %>
|
||||
<% @users.each do |user| %>
|
||||
<tr class="hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
data-controller="clickable-row"
|
||||
data-clickable-row-url-value="<%= user_show_path(user) %>"
|
||||
data-action="click->clickable-row#navigate">
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.id %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.username %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.type %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.email.presence || content_tag(:span, "—", class: "text-gray-400") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<%= user.classroom&.name || content_tag(:span, "—", class: "text-gray-400") %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">
|
||||
<% if user.admin? %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">Yes</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">No</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<div class="flex justify-end gap-2">
|
||||
<%= link_to "View", user_show_path(user), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<%= link_to "Edit", edit_admin_user_path(user), class: "text-gray-600 hover:text-gray-800" %>
|
||||
<%= link_to "Delete", admin_user_path(user),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" },
|
||||
class: "text-red-600 hover:text-red-800" %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="7" class="px-3 py-12 text-center text-sm text-gray-500">
|
||||
<div class="flex flex-col items-center">
|
||||
<i class="fas fa-inbox text-4xl text-gray-300 mb-3"></i>
|
||||
<p class="font-medium">No records found</p>
|
||||
<p class="text-gray-400 mt-1">No users found. Create your first user to get started.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900">New User</h1>
|
||||
</div>
|
||||
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
@@ -0,0 +1,51 @@
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<%= admin_breadcrumbs(@breadcrumbs) %>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg mb-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-900"><%= @user.username %></h2>
|
||||
<div class="flex gap-3">
|
||||
<%= link_to edit_admin_user_path(@user),
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
|
||||
<i class="fas fa-edit -ml-1 mr-2 h-5 w-5 text-gray-500"></i>
|
||||
Edit
|
||||
<% end %>
|
||||
<%= link_to admin_user_path(@user),
|
||||
data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this user?" },
|
||||
class: "inline-flex items-center px-4 py-2 border border-red-300 shadow-sm text-sm font-medium rounded-md text-red-700 bg-white hover:bg-red-50" do %>
|
||||
<i class="fas fa-trash -ml-1 mr-2 h-5 w-5 text-red-500"></i>
|
||||
Delete
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= admin_show_attributes(
|
||||
@user,
|
||||
attributes: %i[id username type email admin classroom created_at updated_at]
|
||||
) %>
|
||||
|
||||
<!-- Associated Portfolio -->
|
||||
<% if @user.portfolio.present? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="text-sm font-medium text-gray-500 mb-3">Portfolio</h3>
|
||||
<div class="text-sm text-gray-900">
|
||||
Portfolio ID: <%= @user.portfolio.id %>
|
||||
<% if @user.student? %>
|
||||
<%= link_to "View Portfolio", user_portfolio_path(@user, @user.portfolio), class: "ml-2 text-blue-600 hover:text-blue-800" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500">No portfolio associated with this user.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= link_to "Back to Users", admin_users_path,
|
||||
class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user