panda-core 0.12.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +53 -0
- data/app/assets/builds/panda_core_admin.css +2 -0
- data/app/assets/tailwind/application.css +236 -36
- data/app/builders/panda/core/form_builder.rb +120 -18
- data/app/components/panda/core/UI/badge.html.erb +10 -0
- data/app/components/panda/core/UI/badge.rb +14 -30
- data/app/components/panda/core/UI/button.html.erb +5 -0
- data/app/components/panda/core/UI/button.rb +11 -15
- data/app/components/panda/core/UI/card.html.erb +3 -0
- data/app/components/panda/core/UI/card.rb +7 -6
- data/app/components/panda/core/admin/attribute_diff_component.html.erb +21 -0
- data/app/components/panda/core/admin/attribute_diff_component.rb +29 -0
- data/app/components/panda/core/admin/breadcrumb_component.html.erb +36 -0
- data/app/components/panda/core/admin/breadcrumb_component.rb +12 -64
- data/app/components/panda/core/admin/breadcrumbs_component.html.erb +16 -0
- data/app/components/panda/core/admin/breadcrumbs_component.rb +26 -0
- data/app/components/panda/core/admin/button_component.html.erb +9 -0
- data/app/components/panda/core/admin/button_component.rb +36 -31
- data/app/components/panda/core/admin/callout_component.html.erb +17 -0
- data/app/components/panda/core/admin/callout_component.rb +57 -0
- data/app/components/panda/core/admin/code_block_component.html.erb +9 -0
- data/app/components/panda/core/admin/code_block_component.rb +23 -0
- data/app/components/panda/core/admin/container_component.html.erb +27 -0
- data/app/components/panda/core/admin/container_component.rb +11 -77
- data/app/components/panda/core/admin/contributors_summary_component.html.erb +15 -0
- data/app/components/panda/core/admin/contributors_summary_component.rb +23 -0
- data/app/components/panda/core/admin/dashboard_default_content_component.html.erb +70 -0
- data/app/components/panda/core/admin/dashboard_default_content_component.rb +32 -0
- data/app/components/panda/core/admin/delete_button_component.html.erb +4 -0
- data/app/components/panda/core/admin/delete_button_component.rb +41 -0
- data/app/components/panda/core/admin/dropdown_button_component.html.erb +28 -0
- data/app/components/panda/core/admin/dropdown_button_component.rb +100 -0
- data/app/components/panda/core/admin/dropdown_component.html.erb +32 -0
- data/app/components/panda/core/admin/dropdown_component.rb +65 -0
- data/app/components/panda/core/admin/empty_state_component.html.erb +16 -0
- data/app/components/panda/core/admin/empty_state_component.rb +25 -0
- data/app/components/panda/core/admin/file_gallery_component.html.erb +58 -0
- data/app/components/panda/core/admin/file_gallery_component.rb +26 -98
- data/app/components/panda/core/admin/filter_pills_component.html.erb +6 -0
- data/app/components/panda/core/admin/filter_pills_component.rb +29 -0
- data/app/components/panda/core/admin/flash_message_component.html.erb +11 -0
- data/app/components/panda/core/admin/flash_message_component.rb +33 -60
- data/app/components/panda/core/admin/form_error_component.html.erb +7 -0
- data/app/components/panda/core/admin/form_error_component.rb +8 -16
- data/app/components/panda/core/admin/form_footer_component.html.erb +16 -0
- data/app/components/panda/core/admin/form_footer_component.rb +65 -0
- data/app/components/panda/core/admin/form_input_component.html.erb +1 -0
- data/app/components/panda/core/admin/form_input_component.rb +21 -13
- data/app/components/panda/core/admin/form_section_component.html.erb +16 -0
- data/app/components/panda/core/admin/form_section_component.rb +64 -0
- data/app/components/panda/core/admin/form_select_component.html.erb +5 -0
- data/app/components/panda/core/admin/form_select_component.rb +25 -26
- data/app/components/panda/core/admin/heading_component.html.erb +26 -0
- data/app/components/panda/core/admin/heading_component.rb +23 -60
- data/app/components/panda/core/admin/login_form_component.html.erb +38 -0
- data/app/components/panda/core/admin/login_form_component.rb +28 -0
- data/app/components/panda/core/admin/main_layout_component.html.erb +88 -0
- data/app/components/panda/core/admin/main_layout_component.rb +19 -0
- data/app/components/panda/core/admin/my_profile/connected_account_component.html.erb +41 -0
- data/app/components/panda/core/admin/my_profile/connected_account_component.rb +27 -0
- data/app/components/panda/core/admin/navigation/item_component.html.erb +34 -0
- data/app/components/panda/core/admin/navigation/item_component.rb +69 -0
- data/app/components/panda/core/admin/navigation/sub_item_component.html.erb +9 -0
- data/app/components/panda/core/admin/navigation/sub_item_component.rb +44 -0
- data/app/components/panda/core/admin/navigation/user_menu_component.html.erb +30 -0
- data/app/components/panda/core/admin/navigation/user_menu_component.rb +36 -0
- data/app/components/panda/core/admin/page_header_component.html.erb +28 -0
- data/app/components/panda/core/admin/page_header_component.rb +19 -35
- data/app/components/panda/core/admin/pagination_component.html.erb +33 -0
- data/app/components/panda/core/admin/pagination_component.rb +56 -0
- data/app/components/panda/core/admin/panel_component.html.erb +19 -0
- data/app/components/panda/core/admin/panel_component.rb +4 -29
- data/app/components/panda/core/admin/prose_panel_component.html.erb +6 -0
- data/app/components/panda/core/admin/prose_panel_component.rb +43 -0
- data/app/components/panda/core/admin/search_bar_component.html.erb +19 -0
- data/app/components/panda/core/admin/search_bar_component.rb +31 -0
- data/app/components/panda/core/admin/search_filter_bar_component.html.erb +22 -0
- data/app/components/panda/core/admin/search_filter_bar_component.rb +27 -0
- data/app/components/panda/core/admin/secret_field_component.html.erb +24 -0
- data/app/components/panda/core/admin/secret_field_component.rb +31 -0
- data/app/components/panda/core/admin/sidebar_component.html.erb +160 -0
- data/app/components/panda/core/admin/sidebar_component.rb +55 -0
- data/app/components/panda/core/admin/slideover_component.html.erb +67 -0
- data/app/components/panda/core/admin/slideover_component.rb +31 -70
- data/app/components/panda/core/admin/statistics_component.html.erb +4 -0
- data/app/components/panda/core/admin/statistics_component.rb +6 -8
- data/app/components/panda/core/admin/tab_bar_component.html.erb +48 -0
- data/app/components/panda/core/admin/tab_bar_component.rb +32 -67
- data/app/components/panda/core/admin/table_component.html.erb +82 -0
- data/app/components/panda/core/admin/table_component.rb +54 -87
- data/app/components/panda/core/admin/tag_badge_component.html.erb +7 -0
- data/app/components/panda/core/admin/tag_badge_component.rb +21 -0
- data/app/components/panda/core/admin/tag_component.html.erb +1 -0
- data/app/components/panda/core/admin/tag_component.rb +51 -20
- data/app/components/panda/core/admin/tag_input_component.html.erb +37 -0
- data/app/components/panda/core/admin/tag_input_component.rb +22 -0
- data/app/components/panda/core/admin/tags_panel_component.html.erb +8 -0
- data/app/components/panda/core/admin/tags_panel_component.rb +33 -0
- data/app/components/panda/core/admin/user_activity_component.html.erb +15 -0
- data/app/components/panda/core/admin/user_activity_component.rb +9 -22
- data/app/components/panda/core/admin/user_display_component.html.erb +8 -0
- data/app/components/panda/core/admin/user_display_component.rb +19 -22
- data/app/components/panda/core/base.rb +35 -42
- data/app/components/panda/core/shared/footer_component.html.erb +2 -0
- data/app/components/panda/core/shared/footer_component.rb +11 -0
- data/app/components/panda/core/shared/header_component.html.erb +32 -0
- data/app/components/panda/core/shared/header_component.rb +19 -0
- data/app/constraints/panda/core/admin_constraint.rb +2 -2
- data/app/controllers/concerns/panda/core/admin_authorization.rb +58 -0
- data/app/controllers/concerns/panda/core/authorizable.rb +107 -0
- data/app/controllers/panda/core/admin/base_controller.rb +62 -3
- data/app/controllers/panda/core/admin/editor/search_controller.rb +17 -0
- data/app/controllers/panda/core/admin/feature_flags_controller.rb +35 -0
- data/app/controllers/panda/core/admin/file_categories_controller.rb +84 -0
- data/app/controllers/panda/core/admin/files_controller.rb +230 -0
- data/app/controllers/panda/core/admin/import_sessions_controller.rb +120 -0
- data/app/controllers/panda/core/admin/my_profile/logins_controller.rb +24 -0
- data/app/controllers/panda/core/admin/my_profile_controller.rb +31 -0
- data/app/controllers/panda/core/admin/search_controller.rb +15 -0
- data/app/controllers/panda/core/admin/sessions_controller.rb +84 -10
- data/app/controllers/panda/core/admin/tags_controller.rb +77 -0
- data/app/controllers/panda/core/admin/test_sessions_controller.rb +5 -6
- data/app/controllers/panda/core/admin/users_controller.rb +184 -0
- data/app/controllers/panda/core/application_controller.rb +13 -1
- data/app/helpers/panda/core/active_storage_helper.rb +24 -0
- data/app/helpers/panda/core/feature_flag_helper.rb +11 -0
- data/app/helpers/panda/core/form_helper.rb +15 -0
- data/app/javascript/panda/core/controllers/avatar_upload_controller.js +73 -0
- data/app/javascript/panda/core/controllers/clipboard_controller.js +119 -0
- data/app/javascript/panda/core/controllers/collapsible_item_controller.js +45 -0
- data/app/javascript/panda/core/controllers/custom_select_controller.js +256 -0
- data/app/javascript/panda/core/controllers/datepicker_controller.js +121 -0
- data/app/javascript/panda/core/controllers/daterange_controller.js +193 -0
- data/app/javascript/panda/core/controllers/file_gallery_controller.js +134 -0
- data/app/javascript/panda/core/controllers/file_upload_controller.js +165 -0
- data/app/javascript/panda/core/controllers/global_search_controller.js +184 -0
- data/app/javascript/panda/core/controllers/index.js +36 -0
- data/app/javascript/panda/core/controllers/mobile_sidebar_controller.js +58 -0
- data/app/javascript/panda/core/controllers/navigation_toggle_controller.js +49 -24
- data/app/javascript/panda/core/controllers/row_link_controller.js +27 -0
- data/app/javascript/panda/core/controllers/tag_input_controller.js +183 -0
- data/app/javascript/panda/core/tailwindcss-stimulus-components.js +2 -3
- data/app/javascript/panda/core/vendor/@fortawesome--fontawesome-free@7.2.0.js +8129 -0
- data/app/javascript/panda/core/vendor/@hotwired--turbo.js +7197 -100
- data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +511 -2
- data/app/javascript/panda/core/vendor/@tailwindplus--elements@1.0.22.js +65 -0
- data/app/javascript/panda/core/vendor/cropperjs@2.1.0.js +3 -0
- data/app/javascript/panda/core/vendor/vanilla-calendar-pro.js +2 -0
- data/app/models/concerns/panda/core/has_metadata.rb +164 -0
- data/app/models/concerns/panda/core/importable.rb +79 -0
- data/app/models/concerns/panda/core/searchable.rb +66 -0
- data/app/models/concerns/panda/core/taggable.rb +37 -0
- data/app/models/panda/core/feature_flag.rb +56 -0
- data/app/models/panda/core/file_categorization.rb +14 -0
- data/app/models/panda/core/file_category.rb +45 -0
- data/app/models/panda/core/import_session.rb +79 -0
- data/app/models/panda/core/presence.rb +24 -0
- data/app/models/panda/core/tag.rb +33 -0
- data/app/models/panda/core/tagging.rb +14 -0
- data/app/models/panda/core/user.rb +94 -16
- data/app/models/panda/core/user_activity.rb +42 -0
- data/app/models/panda/core/user_session.rb +35 -0
- data/app/services/panda/core/attach_avatar_service.rb +15 -3
- data/app/services/panda/core/csv_import_service.rb +65 -0
- data/app/services/panda/core/file_categorizer.rb +52 -0
- data/app/services/panda/core/file_parser.rb +76 -0
- data/app/services/panda/core/invite_user_service.rb +44 -0
- data/app/services/panda/core/presence_service.rb +49 -0
- data/app/views/layouts/panda/core/admin.html.erb +6 -76
- data/app/views/layouts/panda/core/admin_simple.html.erb +2 -2
- data/app/views/panda/core/admin/dashboard/_default_content.html.erb +1 -73
- data/app/views/panda/core/admin/dashboard/show.html.erb +12 -15
- data/app/views/panda/core/admin/feature_flags/index.html.erb +56 -0
- data/app/views/panda/core/admin/file_categories/_form.html.erb +20 -0
- data/app/views/panda/core/admin/file_categories/edit.html.erb +5 -0
- data/app/views/panda/core/admin/file_categories/index.html.erb +55 -0
- data/app/views/panda/core/admin/file_categories/new.html.erb +5 -0
- data/app/views/panda/core/admin/files/_file_details.html.erb +156 -0
- data/app/views/panda/core/admin/files/index.html.erb +61 -0
- data/app/views/panda/core/admin/import_sessions/column_map.html.erb +43 -0
- data/app/views/panda/core/admin/import_sessions/index.html.erb +48 -0
- data/app/views/panda/core/admin/import_sessions/new.html.erb +37 -0
- data/app/views/panda/core/admin/import_sessions/preview.html.erb +51 -0
- data/app/views/panda/core/admin/import_sessions/show.html.erb +87 -0
- data/app/views/panda/core/admin/my_profile/edit.html.erb +51 -39
- data/app/views/panda/core/admin/my_profile/logins/show.html.erb +72 -0
- data/app/views/panda/core/admin/sessions/new.html.erb +1 -37
- data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +1 -18
- data/app/views/panda/core/admin/shared/_sidebar.html.erb +1 -99
- data/app/views/panda/core/admin/shared/_slideover.html.erb +3 -3
- data/app/views/panda/core/admin/tags/_form.html.erb +21 -0
- data/app/views/panda/core/admin/tags/edit.html.erb +4 -0
- data/app/views/panda/core/admin/tags/index.html.erb +41 -0
- data/app/views/panda/core/admin/tags/new.html.erb +4 -0
- data/app/views/panda/core/admin/users/activity.html.erb +90 -0
- data/app/views/panda/core/admin/users/edit.html.erb +48 -0
- data/app/views/panda/core/admin/users/index.html.erb +161 -0
- data/app/views/panda/core/admin/users/sessions.html.erb +103 -0
- data/app/views/panda/core/admin/users/show.html.erb +224 -0
- data/app/views/panda/core/shared/_footer.html.erb +1 -2
- data/app/views/panda/core/shared/_header.html.erb +4 -22
- data/config/brakeman.ignore +68 -31
- data/config/importmap.rb +13 -11
- data/config/routes.rb +49 -2
- data/db/migrate/20260202171614_create_panda_core_file_categories.rb +30 -0
- data/db/migrate/20260203000001_add_user_management_fields_to_panda_core_users.rb +21 -0
- data/db/migrate/20260203000002_create_panda_core_user_activities.rb +24 -0
- data/db/migrate/20260203000003_create_panda_core_user_sessions.rb +25 -0
- data/db/migrate/20260207100000_create_panda_core_presences.rb +21 -0
- data/db/migrate/20260213000001_fix_active_storage_attachments_record_id_type.rb +44 -0
- data/db/migrate/20260217000001_create_panda_core_feature_flags.rb +14 -0
- data/db/migrate/20260303000001_add_metadata_to_panda_core_users.rb +11 -0
- data/db/migrate/20260310000001_create_panda_core_tags.rb +27 -0
- data/db/migrate/20260310000002_create_panda_core_import_sessions.rb +29 -0
- data/lib/generators/panda/core/install_generator.rb +32 -0
- data/lib/generators/panda/core/templates/panda.rb +63 -0
- data/lib/panda/core/asset_loader.rb +5 -2
- data/lib/panda/core/authentication.rb +3 -3
- data/lib/panda/core/configuration.rb +195 -1
- data/lib/panda/core/engine/admin_controller_config.rb +3 -6
- data/lib/panda/core/engine/autoload_config.rb +21 -9
- data/lib/panda/core/engine/omniauth_config.rb +108 -38
- data/lib/panda/core/engine/pending_migrations_check.rb +46 -0
- data/lib/panda/core/engine/sqlite_schema_compatibility.rb +36 -0
- data/lib/panda/core/engine/{phlex_config.rb → view_component_config.rb} +5 -7
- data/lib/panda/core/engine.rb +90 -25
- data/lib/panda/core/middleware.rb +35 -0
- data/lib/panda/core/navigation_registry.rb +455 -0
- data/lib/panda/core/oauth_providers.rb +27 -0
- data/lib/panda/core/permission_registry.rb +89 -0
- data/lib/panda/core/search_registry.rb +69 -0
- data/lib/panda/core/seeds/file_categories.rb +30 -0
- data/lib/panda/core/shared/inflections_config.rb +1 -5
- data/lib/panda/core/testing/rails_helper.rb +87 -0
- data/lib/panda/core/testing/support/authentication_test_helpers.rb +22 -18
- data/lib/panda/core/testing/support/system/cuprite_helpers.rb +4 -4
- data/lib/panda/core/testing/view_component_test_controller.rb +19 -0
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core/widget_registry.rb +50 -0
- data/lib/panda/core.rb +17 -8
- data/lib/tasks/panda/core/file_categories.rake +41 -0
- data/public/panda-core-assets/panda-core-0.12.5.css +2 -0
- data/public/panda-core-assets/panda-core-0.13.0.css +2 -0
- data/public/panda-core-assets/panda-core-0.14.0.css +2 -0
- data/public/panda-core-assets/panda-core-0.14.4.css +2 -0
- data/public/panda-core-assets/panda-core.css +2 -2
- data/public/panda-core-assets/vanilla-calendar.css +47 -0
- metadata +203 -26
- data/lib/panda/core/engine/inflections_config.rb +0 -23
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<%= render Panda::Core::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading_slot(text: "Preview Import", level: 1) %>
|
|
3
|
+
|
|
4
|
+
<%= render Panda::Core::Admin::PanelComponent.new do %>
|
|
5
|
+
<p class="text-sm text-gray-600 mb-4">
|
|
6
|
+
Showing first <%= @preview_rows.size %> rows. Review the mapped data below before importing.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<div class="overflow-x-auto">
|
|
10
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Row</th>
|
|
14
|
+
<% @mapping.each do |csv_col, field_name| %>
|
|
15
|
+
<% defn = @field_definitions.find { |d| d.name.to_s == field_name } %>
|
|
16
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
17
|
+
<%= defn&.label || field_name %>
|
|
18
|
+
</th>
|
|
19
|
+
<% end %>
|
|
20
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
<tbody class="divide-y divide-gray-100">
|
|
24
|
+
<% @preview_rows.each_with_index do |row, i| %>
|
|
25
|
+
<% attrs, errors = @import_session.importable_class.import_row(row, @mapping) %>
|
|
26
|
+
<tr class="<%= errors.any? ? 'bg-red-50' : '' %>">
|
|
27
|
+
<td class="px-3 py-2 text-sm text-gray-500"><%= i + 2 %></td>
|
|
28
|
+
<% @mapping.each do |csv_col, field_name| %>
|
|
29
|
+
<td class="px-3 py-2 text-sm text-gray-900"><%= row[csv_col] %></td>
|
|
30
|
+
<% end %>
|
|
31
|
+
<td class="px-3 py-2 text-sm">
|
|
32
|
+
<% if errors.any? %>
|
|
33
|
+
<span class="text-red-600"><%= errors.join(", ") %></span>
|
|
34
|
+
<% else %>
|
|
35
|
+
<span class="text-green-600"><i class="fa-solid fa-check"></i> OK</span>
|
|
36
|
+
<% end %>
|
|
37
|
+
</td>
|
|
38
|
+
</tr>
|
|
39
|
+
<% end %>
|
|
40
|
+
</tbody>
|
|
41
|
+
</table>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="flex items-center gap-3 mt-6">
|
|
45
|
+
<%= button_to import_admin_import_session_path(@import_session), method: :post, class: "inline-flex items-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary/90 transition cursor-pointer" do %>
|
|
46
|
+
<i class="fa-solid fa-file-import mr-2"></i> Start Import
|
|
47
|
+
<% end %>
|
|
48
|
+
<%= link_to "Back to mapping", column_map_admin_import_session_path(@import_session), class: "text-sm text-gray-500 hover:text-gray-700" %>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
51
|
+
<% end %>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<%= render Panda::Core::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading_slot(text: "Import Results", level: 1) %>
|
|
3
|
+
|
|
4
|
+
<%= render Panda::Core::Admin::PanelComponent.new do %>
|
|
5
|
+
<%# Status badge %>
|
|
6
|
+
<div class="mb-6">
|
|
7
|
+
<% case @import_session.status %>
|
|
8
|
+
<% when "complete" %>
|
|
9
|
+
<span class="inline-flex items-center gap-1.5 rounded-full bg-green-50 px-3 py-1 text-sm font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
|
10
|
+
<i class="fa-solid fa-check-circle"></i> Complete
|
|
11
|
+
</span>
|
|
12
|
+
<% when "importing" %>
|
|
13
|
+
<span class="inline-flex items-center gap-1.5 rounded-full bg-blue-50 px-3 py-1 text-sm font-medium text-blue-700 ring-1 ring-inset ring-blue-600/20">
|
|
14
|
+
<i class="fa-solid fa-spinner fa-spin"></i> Importing...
|
|
15
|
+
</span>
|
|
16
|
+
<% when "failed" %>
|
|
17
|
+
<span class="inline-flex items-center gap-1.5 rounded-full bg-red-50 px-3 py-1 text-sm font-medium text-red-700 ring-1 ring-inset ring-red-600/20">
|
|
18
|
+
<i class="fa-solid fa-exclamation-circle"></i> Failed
|
|
19
|
+
</span>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<%# Stats %>
|
|
24
|
+
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4 mb-6">
|
|
25
|
+
<div class="rounded-lg bg-gray-50 p-4">
|
|
26
|
+
<div class="text-2xl font-semibold text-gray-900"><%= @import_session.total_rows %></div>
|
|
27
|
+
<div class="text-sm text-gray-500">Total rows</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="rounded-lg bg-green-50 p-4">
|
|
30
|
+
<div class="text-2xl font-semibold text-green-700"><%= @import_session.imported_count %></div>
|
|
31
|
+
<div class="text-sm text-green-600">Imported</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="rounded-lg bg-red-50 p-4">
|
|
34
|
+
<div class="text-2xl font-semibold text-red-700"><%= @import_session.error_count %></div>
|
|
35
|
+
<div class="text-sm text-red-600">Errors</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="rounded-lg bg-gray-50 p-4">
|
|
38
|
+
<div class="text-2xl font-semibold text-gray-900"><%= @import_session.skipped_count %></div>
|
|
39
|
+
<div class="text-sm text-gray-500">Skipped</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<%# Progress bar %>
|
|
44
|
+
<% if @import_session.importing? %>
|
|
45
|
+
<div class="mb-6">
|
|
46
|
+
<div class="flex justify-between text-sm text-gray-600 mb-1">
|
|
47
|
+
<span>Progress</span>
|
|
48
|
+
<span><%= @import_session.progress_percentage %>%</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
|
51
|
+
<div class="bg-primary h-2.5 rounded-full transition-all" style="width: <%= @import_session.progress_percentage %>%"></div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
|
|
56
|
+
<%# Errors log %>
|
|
57
|
+
<% if @import_session.errors_log.any? %>
|
|
58
|
+
<div class="mt-6">
|
|
59
|
+
<h3 class="text-sm font-medium text-gray-900 mb-3">Errors (<%= @import_session.errors_log.size %> rows)</h3>
|
|
60
|
+
<div class="max-h-64 overflow-y-auto rounded-lg border border-red-200">
|
|
61
|
+
<table class="min-w-full divide-y divide-red-100">
|
|
62
|
+
<thead class="bg-red-50">
|
|
63
|
+
<tr>
|
|
64
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-red-700">Row</th>
|
|
65
|
+
<th class="px-3 py-2 text-left text-xs font-medium text-red-700">Errors</th>
|
|
66
|
+
</tr>
|
|
67
|
+
</thead>
|
|
68
|
+
<tbody class="divide-y divide-red-50">
|
|
69
|
+
<% @import_session.errors_log.each do |entry| %>
|
|
70
|
+
<tr>
|
|
71
|
+
<td class="px-3 py-2 text-sm text-gray-900"><%= entry["row"] %></td>
|
|
72
|
+
<td class="px-3 py-2 text-sm text-red-600"><%= Array(entry["errors"]).join(", ") %></td>
|
|
73
|
+
</tr>
|
|
74
|
+
<% end %>
|
|
75
|
+
</tbody>
|
|
76
|
+
</table>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<% end %>
|
|
80
|
+
|
|
81
|
+
<div class="mt-6">
|
|
82
|
+
<a href="javascript:history.back()" class="text-sm text-gray-500 hover:text-gray-700">
|
|
83
|
+
<i class="fa-solid fa-arrow-left mr-1"></i> Back
|
|
84
|
+
</a>
|
|
85
|
+
</div>
|
|
86
|
+
<% end %>
|
|
87
|
+
<% end %>
|
|
@@ -1,60 +1,72 @@
|
|
|
1
1
|
<%= render Panda::Core::Admin::ContainerComponent.new do |component| %>
|
|
2
|
-
<% component.
|
|
2
|
+
<% component.with_heading_slot(text: "My Profile", level: 1) %>
|
|
3
3
|
|
|
4
|
-
<%=
|
|
4
|
+
<%= panda_form_with model: user,
|
|
5
5
|
url: admin_my_profile_path,
|
|
6
6
|
method: :patch,
|
|
7
7
|
local: true,
|
|
8
|
+
html: { multipart: true },
|
|
8
9
|
data: { controller: "theme-form" } do |f| %>
|
|
9
10
|
<%= render Panda::Core::Admin::FormErrorComponent.new(model: user) %>
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<div class="
|
|
14
|
-
|
|
15
|
-
<div class="
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<
|
|
12
|
+
<%= render Panda::Core::Admin::PanelComponent.new do |panel| %>
|
|
13
|
+
<% panel.with_heading_slot { "Profile Settings" } %>
|
|
14
|
+
<div class="space-y-4">
|
|
15
|
+
<!-- Avatar Section -->
|
|
16
|
+
<div class="col-span-full" data-controller="avatar-upload">
|
|
17
|
+
<span id="avatar-label" class="block text-sm/6 font-medium text-gray-900 dark:text-white">Profile Picture</span>
|
|
18
|
+
<div class="mt-2 flex items-center gap-x-3">
|
|
19
|
+
<% avatar = user.avatar_url %>
|
|
20
|
+
<img src="<%= avatar.presence || 'about:blank' %>"
|
|
21
|
+
alt="<%= user.name %>"
|
|
22
|
+
class="w-48 h-48 max-w-48 rounded-full object-cover<%= ' hidden' unless avatar.present? %>"
|
|
23
|
+
data-avatar-upload-target="preview">
|
|
24
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"
|
|
25
|
+
class="w-48 h-48 text-gray-300 dark:text-gray-500<%= ' hidden' if avatar.present? %>"
|
|
26
|
+
data-avatar-upload-target="placeholder">
|
|
20
27
|
<path d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
21
28
|
</svg>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
<div>
|
|
30
|
+
<label for="user_avatar" class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs inset-ring inset-ring-gray-300 hover:bg-gray-50 cursor-pointer dark:bg-white/10 dark:text-white dark:shadow-none dark:inset-ring-white/5 dark:hover:bg-white/20">
|
|
31
|
+
Change
|
|
32
|
+
</label>
|
|
33
|
+
<% if user.avatar.respond_to?(:blob) && user.avatar.blob.present? %>
|
|
34
|
+
<% blob = user.avatar.blob %>
|
|
35
|
+
<p class="mt-2 text-xs text-gray-600 dark:text-gray-400">
|
|
36
|
+
Current: <%= blob.filename %> (<%= number_to_human_size(blob.byte_size) %>)
|
|
37
|
+
</p>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
|
40
|
+
<input type="file"
|
|
41
|
+
id="user_avatar"
|
|
42
|
+
name="user[avatar]"
|
|
43
|
+
accept="image/png,image/jpeg,image/jpg,image/gif,image/webp"
|
|
44
|
+
class="sr-only"
|
|
45
|
+
aria-labelledby="avatar-label"
|
|
46
|
+
data-avatar-upload-target="input"
|
|
47
|
+
data-action="change->avatar-upload#handleFileSelect">
|
|
48
|
+
</div>
|
|
49
|
+
<!-- File info (shown when a new file is selected) -->
|
|
50
|
+
<div class="hidden mt-3 flex items-center gap-x-3 text-sm text-gray-600 dark:text-gray-400" data-avatar-upload-target="fileInfo">
|
|
51
|
+
<span data-avatar-upload-target="fileName"></span>
|
|
52
|
+
<span data-avatar-upload-target="fileSize"></span>
|
|
53
|
+
<button type="button"
|
|
54
|
+
class="text-red-600 hover:text-red-500 dark:text-red-400 dark:hover:text-red-300 font-medium"
|
|
55
|
+
data-action="avatar-upload#remove">
|
|
56
|
+
Remove
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
29
59
|
</div>
|
|
30
|
-
<% if user.avatar.attached? %>
|
|
31
|
-
<p class="mt-2 text-xs text-gray-600 dark:text-gray-400">
|
|
32
|
-
Current: <%= user.avatar.filename %> (<%= number_to_human_size(user.avatar.byte_size) %>)
|
|
33
|
-
</p>
|
|
34
|
-
<% end %>
|
|
35
|
-
</div>
|
|
36
60
|
|
|
37
|
-
<div class="field">
|
|
38
|
-
<%= f.label :name %>
|
|
39
61
|
<%= f.text_field :name %>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<div class="field">
|
|
43
|
-
<%= f.label :email %>
|
|
44
62
|
<%= f.email_field :email %>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div class="field">
|
|
48
|
-
<%= f.label :current_theme, "Theme" %>
|
|
49
63
|
<%= f.select :current_theme,
|
|
50
64
|
options_for_select(Panda::Core.config.available_themes || [["Default", "default"], ["Sky", "sky"]], user.current_theme),
|
|
51
|
-
{},
|
|
65
|
+
{ label: "Theme" },
|
|
52
66
|
data: { action: "change->theme-form#updateTheme" } %>
|
|
53
67
|
</div>
|
|
54
|
-
|
|
68
|
+
<% end %>
|
|
55
69
|
|
|
56
|
-
<%=
|
|
57
|
-
class: "btn btn-primary mt-4",
|
|
58
|
-
data: { disable_with: "Saving..." } %>
|
|
70
|
+
<%= render Panda::Core::Admin::FormFooterComponent.new(submit_text: "Update Profile") %>
|
|
59
71
|
<% end %>
|
|
60
72
|
<% end %>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<%= render Panda::Core::Admin::ContainerComponent.new do %>
|
|
2
|
+
<%= render Panda::Core::Admin::HeadingComponent.new(level: 1, text: "Login & Security") %>
|
|
3
|
+
|
|
4
|
+
<!-- Connected Accounts -->
|
|
5
|
+
<%= render Panda::Core::Admin::PanelComponent.new do |panel| %>
|
|
6
|
+
<% panel.with_heading_slot do %>
|
|
7
|
+
<div class="flex flex-col">
|
|
8
|
+
<span>Connected Accounts</span>
|
|
9
|
+
<span class="text-sm font-normal text-white/70">Manage your OAuth authentication providers</span>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% panel.with_body_slot do %>
|
|
13
|
+
<div class="space-y-4">
|
|
14
|
+
<% enabled_providers = Panda::Core.config.authentication_providers %>
|
|
15
|
+
<% if enabled_providers.empty? %>
|
|
16
|
+
<p class="text-sm text-gray-500">No authentication providers are currently enabled.</p>
|
|
17
|
+
<% else %>
|
|
18
|
+
<% enabled_providers.each do |provider_name, _config| %>
|
|
19
|
+
<%= render Panda::Core::Admin::MyProfile::ConnectedAccountComponent.new(
|
|
20
|
+
provider: provider_name,
|
|
21
|
+
user: user
|
|
22
|
+
) %>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
<% end %>
|
|
27
|
+
<% end %>
|
|
28
|
+
|
|
29
|
+
<!-- Login History -->
|
|
30
|
+
<%= render Panda::Core::Admin::PanelComponent.new do |panel| %>
|
|
31
|
+
<% panel.with_heading_slot do %>
|
|
32
|
+
<div class="flex flex-col">
|
|
33
|
+
<span>Login History</span>
|
|
34
|
+
<span class="text-sm font-normal text-white/70">Recent login activity for your account</span>
|
|
35
|
+
</div>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% panel.with_body_slot do %>
|
|
38
|
+
<div class="space-y-4">
|
|
39
|
+
<p class="text-sm text-gray-500">Login history tracking will be implemented in a future release.</p>
|
|
40
|
+
<!-- Future implementation:
|
|
41
|
+
- Table showing date/time, location, IP address, device/browser
|
|
42
|
+
- Filter/search capabilities
|
|
43
|
+
- Export functionality
|
|
44
|
+
-->
|
|
45
|
+
</div>
|
|
46
|
+
<% end %>
|
|
47
|
+
<% end %>
|
|
48
|
+
|
|
49
|
+
<!-- Two-Factor Authentication (Future) -->
|
|
50
|
+
<%= render Panda::Core::Admin::PanelComponent.new do |panel| %>
|
|
51
|
+
<% panel.with_heading_slot do %>
|
|
52
|
+
<div class="flex flex-col">
|
|
53
|
+
<span>Two-Factor Authentication</span>
|
|
54
|
+
<span class="text-sm font-normal text-white/70">Add an extra layer of security to your account</span>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
<% panel.with_body_slot do %>
|
|
58
|
+
<div class="space-y-4">
|
|
59
|
+
<p class="text-sm text-gray-500">
|
|
60
|
+
Two-factor authentication will be available in a future release.
|
|
61
|
+
Track progress in <a href="https://github.com/tastybamboo/panda-core/issues/34" class="text-blue-600 hover:text-blue-800 underline" target="_blank">GitHub Issue #34</a>.
|
|
62
|
+
</p>
|
|
63
|
+
<!-- Future implementation:
|
|
64
|
+
- TOTP setup with QR code
|
|
65
|
+
- Backup codes generation
|
|
66
|
+
- SMS fallback option (optional)
|
|
67
|
+
- Recovery options
|
|
68
|
+
-->
|
|
69
|
+
</div>
|
|
70
|
+
<% end %>
|
|
71
|
+
<% end %>
|
|
72
|
+
<% end %>
|
|
@@ -1,37 +1 @@
|
|
|
1
|
-
|
|
2
|
-
<div class="text-center sm:mx-auto sm:w-full sm:max-w-sm">
|
|
3
|
-
<% if Panda::Core.config.login_logo_path %>
|
|
4
|
-
<img src="<%= Panda::Core.config.login_logo_path %>" class="py-2 mx-auto w-auto h-32">
|
|
5
|
-
<% end %>
|
|
6
|
-
<h2 class="mt-10 mb-6 text-2xl font-bold text-center text-white">
|
|
7
|
-
<%= Panda::Core.config.login_page_title || "Sign in to your account" %>
|
|
8
|
-
</h2>
|
|
9
|
-
</div>
|
|
10
|
-
<% if @providers&.any? %>
|
|
11
|
-
<% @providers.each do |provider| %>
|
|
12
|
-
<% provider_config = Panda::Core.config.authentication_providers[provider] %>
|
|
13
|
-
<% provider_path = provider_config&.dig(:path_name) || provider %>
|
|
14
|
-
<div class="mt-4 text-center sm:mx-auto sm:w-full sm:max-w-sm">
|
|
15
|
-
<%= form_tag "#{Panda::Core.config.admin_path}/auth/#{provider_path}", method: "post", data: {turbo: false} do %>
|
|
16
|
-
<button type="submit" id="button-sign-in-<%= provider_path %>" class="inline-flex gap-x-2 items-center py-2.5 px-3.5 mx-auto mb-4 bg-white text-gray-900 rounded-md border min-w-56 border-neutral-400 hover:bg-gray-50">
|
|
17
|
-
<i class="<%= oauth_provider_non_brand?(provider) ? 'fa-solid' : 'fa-brands' %> fa-<%= oauth_provider_icon(provider) %> text-xl mr-1"></i>
|
|
18
|
-
Sign in with <%= oauth_provider_name(provider, provider_config) %>
|
|
19
|
-
</button>
|
|
20
|
-
<% end %>
|
|
21
|
-
</div>
|
|
22
|
-
<% end %>
|
|
23
|
-
<% else %>
|
|
24
|
-
<div class="rounded-md bg-yellow-50 p-4 sm:mx-auto sm:w-full sm:max-w-sm">
|
|
25
|
-
<div class="flex">
|
|
26
|
-
<div class="ml-3">
|
|
27
|
-
<h3 class="text-sm font-medium text-yellow-800">
|
|
28
|
-
No authentication providers configured
|
|
29
|
-
</h3>
|
|
30
|
-
<div class="mt-2 text-sm text-yellow-700">
|
|
31
|
-
<p>Please configure at least one authentication provider in your Panda Core configuration.</p>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
<% end %>
|
|
37
|
-
</div>
|
|
1
|
+
<%= render Panda::Core::Admin::LoginFormComponent.new(providers: @providers) %>
|
|
@@ -1,18 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<nav aria-label="Breadcrumb" id="panda-breadcrumbs" class="px-4 w-full sm:px-6 py-2.5">
|
|
4
|
-
<ol role="list">
|
|
5
|
-
<li class="inline-block">
|
|
6
|
-
<a href="<%= Panda::Core.config.admin_path %>" class="<%= breadcrumb_styles %>">
|
|
7
|
-
<i class="fa fa-solid fa-house fa-fw inline-block w-4 text-center"></i>
|
|
8
|
-
<span class="sr-only">Home</span>
|
|
9
|
-
</a>
|
|
10
|
-
</li>
|
|
11
|
-
<% breadcrumbs.each do |crumb| %>
|
|
12
|
-
<li class="inline-block">
|
|
13
|
-
<i class="fa-solid fa-chevron-right fa-fw inline-block w-4 text-center font-light <%= breadcrumb_styles %> px-2"></i>
|
|
14
|
-
<a href="<%= crumb.path %>" class="text-sm font-normal <%= breadcrumb_styles %>"><%= crumb.name %></a>
|
|
15
|
-
</li>
|
|
16
|
-
<% end %>
|
|
17
|
-
</ol>
|
|
18
|
-
</nav>
|
|
1
|
+
<%= render Panda::Core::Admin::BreadcrumbsComponent.new(breadcrumbs: breadcrumbs) %>
|
|
@@ -1,99 +1 @@
|
|
|
1
|
-
|
|
2
|
-
<ul role="list" class="flex flex-col flex-1">
|
|
3
|
-
<a class="block p-0 mt-4 mb-4 ml-2 text-xl font-medium text-white"><%= Panda::Core.config.admin_title || "Panda Admin" %></a>
|
|
4
|
-
<%
|
|
5
|
-
# Get navigation items in original order for display
|
|
6
|
-
nav_items = Panda::Core.config.admin_navigation_items&.call(current_user) || []
|
|
7
|
-
|
|
8
|
-
# Helper to recursively collect all paths from nav items
|
|
9
|
-
def collect_paths(items)
|
|
10
|
-
items.flat_map do |item|
|
|
11
|
-
paths = item[:path] ? [item[:path]] : []
|
|
12
|
-
paths += collect_paths(item[:children] || [])
|
|
13
|
-
paths
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Find the most specific matching path by checking longest paths first
|
|
18
|
-
all_paths = collect_paths(nav_items)
|
|
19
|
-
active_path = all_paths
|
|
20
|
-
.sort_by { |path| -path.length }
|
|
21
|
-
.find { |path| request.path == path || request.path.starts_with?(path + "/") }
|
|
22
|
-
%>
|
|
23
|
-
<% nav_items.each_with_index do |item, index| %>
|
|
24
|
-
<li>
|
|
25
|
-
<%
|
|
26
|
-
has_children = item[:children].present?
|
|
27
|
-
|
|
28
|
-
# For items with children, check if any child is active
|
|
29
|
-
if has_children
|
|
30
|
-
is_active = item[:children].any? do |child|
|
|
31
|
-
child[:path] && (request.path == child[:path] || request.path.starts_with?(child[:path] + "/"))
|
|
32
|
-
end
|
|
33
|
-
else
|
|
34
|
-
is_active = item[:path] == active_path
|
|
35
|
-
end
|
|
36
|
-
%>
|
|
37
|
-
|
|
38
|
-
<% if has_children %>
|
|
39
|
-
<div class="space-y-1" data-controller="navigation-toggle">
|
|
40
|
-
<button type="button"
|
|
41
|
-
data-navigation-toggle-target="button"
|
|
42
|
-
data-action="click->navigation-toggle#toggle"
|
|
43
|
-
aria-controls="sub-menu-<%= index %>"
|
|
44
|
-
aria-expanded="false"
|
|
45
|
-
class="<%= is_active ? 'bg-primary-500 text-white' : 'text-white hover:bg-primary-500/60' %> transition-all group flex items-center w-full gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal">
|
|
46
|
-
<span class="text-center w-6"><i class="<%= item[:icon] %> text-xl fa-fw"></i></span>
|
|
47
|
-
<span class="flex-1 text-left"><%= item[:label] %></span>
|
|
48
|
-
<i class="fa-solid fa-chevron-right text-xs transition-transform duration-150 ease-in-out"
|
|
49
|
-
data-navigation-toggle-target="icon"></i>
|
|
50
|
-
</button>
|
|
51
|
-
<div id="sub-menu-<%= index %>"
|
|
52
|
-
class="space-y-1 hidden"
|
|
53
|
-
style="display: none;"
|
|
54
|
-
data-navigation-toggle-target="menu">
|
|
55
|
-
<% item[:children].each do |child| %>
|
|
56
|
-
<%
|
|
57
|
-
child_is_active = child[:path] && (request.path == child[:path] || request.path.starts_with?(child[:path] + "/"))
|
|
58
|
-
%>
|
|
59
|
-
<%= link_to child[:path], class: "#{child_is_active ? 'bg-primary-500 text-white' : 'text-white hover:bg-primary-500/60'} group flex items-center w-full py-2 pr-2 pl-11 rounded-md text-sm font-normal transition-all" do %>
|
|
60
|
-
<%= child[:label] %>
|
|
61
|
-
<% end %>
|
|
62
|
-
<% end %>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
<% else %>
|
|
66
|
-
<%= link_to item[:path], class: "#{is_active ? "bg-primary-500 text-white relative flex items-center transition-all py-3 px-2 mb-2 rounded-md group gap-x-3 text-base leading-6 font-normal" : "text-white hover:bg-primary-500/60 transition-all group flex items-center gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal"}" do %>
|
|
67
|
-
<span class="text-center w-6"><i class="<%= item[:icon] %> text-xl fa-fw"></i></span>
|
|
68
|
-
<span><%= item[:label] %></span>
|
|
69
|
-
<% end %>
|
|
70
|
-
<% end %>
|
|
71
|
-
</li>
|
|
72
|
-
<% end %>
|
|
73
|
-
<li>
|
|
74
|
-
<%= button_to panda_core.admin_logout_path, method: :delete, id: "logout-link", data: { turbo: false }, class: "text-white hover:bg-primary-500/60 transition-all group flex items-center gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal w-full" do %>
|
|
75
|
-
<span class="text-center w-6"><i class="text-xl fa-solid fa-door-open fa-fw"></i></span>
|
|
76
|
-
<span>Logout</span>
|
|
77
|
-
<% end %>
|
|
78
|
-
</li>
|
|
79
|
-
<li class="mt-auto">
|
|
80
|
-
<%
|
|
81
|
-
# Check if we're in the my_profile section
|
|
82
|
-
is_my_profile_active = request.path.starts_with?("#{Panda::Core.config.admin_path}/my_profile")
|
|
83
|
-
%>
|
|
84
|
-
<%= link_to panda_core.admin_my_profile_path, class: "#{is_my_profile_active ? 'bg-primary-500 text-white' : 'text-white hover:bg-primary-500/60'} transition-all group flex items-center gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal w-full", title: "My Profile" do %>
|
|
85
|
-
<% if current_user.avatar.attached? %>
|
|
86
|
-
<span class="text-center w-6"><%= image_tag main_app.url_for(current_user.avatar), alt: current_user.name, class: "w-auto h-7 rounded-full object-cover" %></span>
|
|
87
|
-
<% elsif !current_user.image_url.to_s.empty? %>
|
|
88
|
-
<span class="text-center w-6"><img src="<%= current_user.image_url %>" class="w-auto h-7 rounded-full"></span>
|
|
89
|
-
<% else %>
|
|
90
|
-
<span class="text-center w-6"><i class="text-xl fa-solid fa-circle-user fa-fw"></i></span>
|
|
91
|
-
<% end %>
|
|
92
|
-
<span><%= current_user.name %></span>
|
|
93
|
-
<% end %>
|
|
94
|
-
</li>
|
|
95
|
-
<li class="px-2 py-3">
|
|
96
|
-
<span class="text-xs text-white">Panda Core v<%= Panda::Core::VERSION %></span>
|
|
97
|
-
</li>
|
|
98
|
-
</ul>
|
|
99
|
-
</nav>
|
|
1
|
+
<%= render Panda::Core::Admin::SidebarComponent.new(user: current_user) %>
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
<div class="fixed inset-0 flex">
|
|
4
4
|
<div class="relative flex w-full max-w-xs flex-1 flex-col bg-gray-800">
|
|
5
5
|
<div class="absolute top-0 right-0 -mr-12 pt-2">
|
|
6
|
-
<button type="button" class="ml-1 flex
|
|
7
|
-
<span
|
|
8
|
-
<svg class="h-
|
|
6
|
+
<button type="button" class="ml-1 flex items-center gap-1 rounded-full bg-white/10 px-2 py-1 text-xs text-white/80 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
|
7
|
+
<span>Close</span>
|
|
8
|
+
<svg class="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
|
9
9
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
10
10
|
</svg>
|
|
11
11
|
</button>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= render Panda::Core::Admin::PanelComponent.new do %>
|
|
2
|
+
<%= panda_form_with(model: tag, url: tag.persisted? ? admin_tag_path(tag) : admin_tags_path) do |f| %>
|
|
3
|
+
<div class="space-y-4">
|
|
4
|
+
<%= f.text_field :name, label: "Name" %>
|
|
5
|
+
|
|
6
|
+
<div>
|
|
7
|
+
<%= f.label :colour, "Colour" %>
|
|
8
|
+
<div class="flex items-center gap-3 mt-1">
|
|
9
|
+
<input type="color" name="tag[colour]" value="<%= tag.colour || '#6b7280' %>"
|
|
10
|
+
class="h-10 w-14 rounded border border-gray-300 cursor-pointer">
|
|
11
|
+
<span class="text-sm text-gray-500">Choose a colour for the tag badge</span>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="mt-6 flex items-center gap-3">
|
|
17
|
+
<%= f.submit tag.persisted? ? "Update Tag" : "Create Tag", class: "inline-flex items-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary/90 transition cursor-pointer" %>
|
|
18
|
+
<%= link_to "Cancel", admin_tags_path, class: "text-sm text-gray-500 hover:text-gray-700" %>
|
|
19
|
+
</div>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<%= render Panda::Core::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading_slot(text: "Tags", level: 1) do %>
|
|
3
|
+
<%= link_to new_admin_tag_path, class: "inline-flex items-center gap-1.5 rounded-lg bg-primary px-3.5 py-2 text-sm font-medium text-white hover:bg-primary/90 transition" do %>
|
|
4
|
+
<i class="fa-solid fa-plus"></i> New Tag
|
|
5
|
+
<% end %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<% if @tags.any? %>
|
|
9
|
+
<%= render Panda::Core::Admin::TableComponent.new(term: "tag", rows: @tags, icon: "fa-solid fa-tag") do |table| %>
|
|
10
|
+
<% table.column("Tag", width: "40%") do |tag| %>
|
|
11
|
+
<div class="flex items-center gap-2">
|
|
12
|
+
<span class="w-3 h-3 rounded-full flex-shrink-0" style="background-color: <%= tag.display_colour %>"></span>
|
|
13
|
+
<span class="font-medium"><%= tag.name %></span>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<% table.column("Used", width: "20%") do |tag| %>
|
|
18
|
+
<span class="text-sm text-gray-500"><%= pluralize(tag.taggings_count, "record") %></span>
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
21
|
+
<% table.column("", width: "40%") do |tag| %>
|
|
22
|
+
<div class="flex justify-end gap-2">
|
|
23
|
+
<%= link_to edit_admin_tag_path(tag), class: "inline-flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700" do %>
|
|
24
|
+
<i class="fa-solid fa-pen-to-square"></i> Edit
|
|
25
|
+
<% end %>
|
|
26
|
+
<%= button_to admin_tag_path(tag), method: :delete, class: "inline-flex items-center gap-1 text-sm text-red-500 hover:text-red-700", data: {turbo_confirm: "Delete tag \"#{tag.name}\"?"} do %>
|
|
27
|
+
<i class="fa-solid fa-trash"></i> Delete
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% else %>
|
|
33
|
+
<%= render Panda::Core::Admin::EmptyStateComponent.new(
|
|
34
|
+
title: "No tags yet",
|
|
35
|
+
description: "Tags help you organise and filter your records.",
|
|
36
|
+
icon: "fa-solid fa-tag",
|
|
37
|
+
action_text: "Create your first tag",
|
|
38
|
+
action_path: new_admin_tag_path
|
|
39
|
+
) %>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|