Quellcode durchsuchen

feat: add per-shop content routing with /content/:shopId

- Add route handling for /content/:shopId in Router
- Modify ContentPage to accept optional shopId parameter
- Auto-load shop details and content when shopId is provided
- Hide shop selection and show filters/content automatically for per-shop view
- Add back button to navigate to all content
- Update page title to show shop name when viewing specific shop

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh vor 8 Monaten
Ursprung
Commit
1a2d4b7cb4
2 geänderte Dateien mit 67 neuen und 8 gelöschten Zeilen
  1. 6 0
      web/src/app/Router.ts
  2. 61 8
      web/src/components/pages/ContentPage.ts

+ 6 - 0
web/src/app/Router.ts

@@ -64,6 +64,11 @@ export class Router {
       }
     } else if (path === '/jobs') {
       component = new JobsPage(this.apiService, this.toastService, this);
+    } else if (path.startsWith('/content/') && !path.endsWith('/content')) {
+      const shopId = path.split('/content/')[1];
+      if (shopId) {
+        component = new ContentPage(this.apiService, this.toastService, this, shopId);
+      }
     } else if (path === '/content') {
       component = new ContentPage(this.apiService, this.toastService, this);
     } else if (path === '/schedules') {
@@ -102,6 +107,7 @@ export class Router {
     if (path === '/shops') return 'Shops';
     if (path.startsWith('/shops/')) return 'Shop Details';
     if (path === '/jobs') return 'Jobs';
+    if (path.startsWith('/content/')) return 'Shop Content';
     if (path === '/content') return 'Content';
     if (path === '/schedules') return 'Schedules';
     if (path === '/webhooks') return 'Webhooks';

+ 61 - 8
web/src/components/pages/ContentPage.ts

@@ -16,12 +16,19 @@ export class ContentPage {
   private totalItems: number = 0;
   private eventsbound: boolean = false;
   private currentModalItem: ShopContent | null = null;
+  private shopName: string | null = null;
 
   constructor(
     private apiService: ApiService,
     private toastService: ToastService,
-    private router: Router
-  ) {}
+    private router: Router,
+    private shopId?: string
+  ) {
+    // If shopId is provided, set it as selected
+    if (shopId) {
+      this.selectedShopId = shopId;
+    }
+  }
 
   render(): HTMLElement {
     this.element = document.createElement('div');
@@ -31,17 +38,25 @@ export class ContentPage {
       <!-- Header -->
       <div class="md:flex md:items-center md:justify-between">
         <div class="flex-1 min-w-0">
+          ${this.shopId ? `
+            <div class="mb-2">
+              <button class="text-sm text-primary-600 dark:text-primary-400 hover:underline inline-flex items-center" id="back-to-all-content">
+                <div class="w-4 h-4 mr-1">${getIcon('arrow-left')}</div>
+                Back to All Content
+              </button>
+            </div>
+          ` : ''}
           <h2 class="text-2xl font-bold leading-7 text-gray-900 dark:text-white sm:text-3xl sm:truncate">
-            Content Browser
+            ${this.shopId && this.shopName ? this.shopName + ' - Content' : 'Content Browser'}
           </h2>
           <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
-            Browse scraped content by shop and category
+            ${this.shopId ? 'Browse content for this shop' : 'Browse scraped content by shop and category'}
           </p>
         </div>
       </div>
 
       <!-- Shop Selection -->
-      <div class="card">
+      <div class="card" id="shop-selection-section" style="display: ${this.shopId ? 'none' : 'block'};">
         <div class="card-header">
           <h3 class="text-lg font-medium text-gray-900 dark:text-white">Select Shop</h3>
           <p class="text-sm text-gray-500 dark:text-gray-400">Choose a shop to view its content</p>
@@ -57,7 +72,7 @@ export class ContentPage {
       </div>
 
       <!-- Filters -->
-      <div class="card" id="filters-section" style="display: none;">
+      <div class="card" id="filters-section" style="display: ${this.shopId ? 'block' : 'none'};">
         <div class="card-header">
           <h3 class="text-lg font-medium text-gray-900 dark:text-white">Filters</h3>
           <p class="text-sm text-gray-500 dark:text-gray-400">Filter content by type and date range</p>
@@ -99,7 +114,7 @@ export class ContentPage {
       </div>
 
       <!-- Content Results -->
-      <div class="card" id="content-section" style="display: none;">
+      <div class="card" id="content-section" style="display: ${this.shopId ? 'block' : 'none'};">
         <div class="card-header">
           <h3 class="text-lg font-medium text-gray-900 dark:text-white">Content Results</h3>
           <p class="text-sm text-gray-500 dark:text-gray-400" id="results-summary">
@@ -201,7 +216,15 @@ export class ContentPage {
     `;
 
     this.bindEvents();
-    this.loadShops();
+
+    // If shopId is provided, load shop details and content directly
+    // Otherwise, load shop selection
+    if (this.shopId) {
+      this.loadShopDetails();
+      this.loadContent();
+    } else {
+      this.loadShops();
+    }
 
     return this.element;
   }
@@ -210,6 +233,12 @@ export class ContentPage {
     if (!this.element || this.eventsbound) return;
     this.eventsbound = true;
 
+    // Back to all content button
+    const backBtn = this.element.querySelector('#back-to-all-content');
+    backBtn?.addEventListener('click', () => {
+      this.router.navigate('/content');
+    });
+
     // Filter form
     const form = this.element.querySelector('#filter-form');
     form?.addEventListener('submit', (e) => {
@@ -289,6 +318,30 @@ export class ContentPage {
     }
   }
 
+  private async loadShopDetails(): Promise<void> {
+    if (!this.shopId) return;
+
+    try {
+      const response = await this.apiService.getShop(this.shopId);
+
+      if (response.success && response.data) {
+        this.shopName = getDomainFromUrl(response.data.shop.url);
+        // Update the header with shop name
+        this.updateHeader();
+      }
+    } catch (error) {
+      // Silently fail - shop name is not critical
+      console.error('Failed to load shop details:', error);
+    }
+  }
+
+  private updateHeader(): void {
+    const header = this.element?.querySelector('h2');
+    if (header && this.shopId && this.shopName) {
+      header.textContent = `${this.shopName} - Content`;
+    }
+  }
+
   private renderShopSelection(): void {
     const container = this.element?.querySelector('#shop-selection');
     if (!container) return;