# Letzshop Jobs & Tables Improvements
Implementation plan for improving the Letzshop management page jobs display and table harmonization.
## Status: Completed
### Completed
- [x] Phase 1: Job Details Modal (commit cef80af)
- [x] Phase 2: Add store column to jobs table
- [x] Phase 3: Platform settings system (rows per page)
- [x] Phase 4: Numbered pagination for jobs table
- [x] Phase 5: Admin customer management page
---
## Overview
This plan addresses 6 improvements:
1. Job details modal with proper display
2. Tab visibility fix when filters cleared
3. Add store column to jobs table
4. Harmonize all tables with table macro
5. Platform-wide rows per page setting
6. Build admin customer page
---
## 1. Job Details Modal
### Current Issue
- "View Details" shows a browser alert instead of a proper modal
- No detailed breakdown of export results
### Requirements
- Create a proper modal for job details
- For exports: show products exported per language file
- Show store name/code
- Show full timestamps and duration
- Show error details if any
### Implementation
#### 1.1 Create Job Details Modal Template
**File:** `app/templates/admin/partials/letzshop-jobs-table.html`
Add modal after the table:
```html
Job ID: #
Type:
Status:
Store:
Started:
Completed:
Duration:
Export Details
Products exported:
```
#### 1.2 Update JavaScript State
**File:** `static/admin/js/marketplace-letzshop.js`
Add state variables:
```javascript
showJobDetailsModal: false,
selectedJobDetails: null,
```
Update `viewJobDetails` method:
```javascript
viewJobDetails(job) {
this.selectedJobDetails = job;
this.showJobDetailsModal = true;
},
```
#### 1.3 Update API to Return Full Details
**File:** `app/services/letzshop/order_service.py`
Update `list_letzshop_jobs` to include `error_details` in the response for export jobs.
---
## 2. Tab Visibility Fix
### Current Issue
- When store filter is cleared, only 2 tabs appear (Orders, Exceptions)
- Should show all tabs: Products, Orders, Exceptions, Jobs, Settings
### Root Cause
- Products, Jobs, and Settings tabs are wrapped in ``
- This is intentional for store-specific features
### Decision Required
**Option A:** Keep current behavior (store-specific tabs hidden when no store)
- Products, Jobs, Settings require a store context
- Cross-store view only shows Orders and Exceptions
**Option B:** Show all tabs but with "Select store" message
- All tabs visible
- Content shows prompt to select store
### Recommended: Option A (Current Behavior)
The current behavior is correct because:
- Products tab shows store's Letzshop products (needs store)
- Jobs tab shows store's jobs (needs store)
- Settings tab configures store's Letzshop (needs store)
- Orders and Exceptions can work cross-store
**No change needed** - document this as intentional behavior.
---
## 3. Add Store Column to Jobs Table
### Requirements
- Add store name/code column to jobs table
- Useful when viewing cross-store (future feature)
- Prepare for reusable jobs component
### Implementation
#### 3.1 Update API Response
**File:** `app/services/letzshop/order_service.py`
Add store info to job dicts:
```python
# In list_letzshop_jobs, add to each job dict:
"store_id": store_id,
"store_name": store.name if store else None,
"store_code": store.store_code if store else None,
```
Need to fetch store once at start of function.
#### 3.2 Update Table Template
**File:** `app/templates/admin/partials/letzshop-jobs-table.html`
Add column header:
```html
Store |
```
Add column data:
```html
|
```
#### 3.3 Update Schema
**File:** `models/schema/letzshop.py`
Update `LetzshopJobItem` to include store fields:
```python
store_id: int | None = None
store_name: str | None = None
store_code: str | None = None
```
---
## 4. Harmonize Tables with Table Macro
### Current State
- Different tables use different pagination styles
- Some use simple prev/next, others use numbered
- Inconsistent styling
### Requirements
- All tables use `table` macro from `shared/macros/tables.html`
- Numbered pagination with page numbers
- Consistent column styling
- Rows per page selector
### Tables to Update
| Table | File | Current Pagination |
|-------|------|-------------------|
| Jobs | letzshop-jobs-table.html | Simple prev/next |
| Products | letzshop-products-tab.html | Simple prev/next |
| Orders | letzshop-orders-tab.html | Simple prev/next |
| Exceptions | letzshop-exceptions-tab.html | Simple prev/next |
### Implementation
#### 4.1 Create/Update Table Macro
**File:** `app/templates/shared/macros/tables.html`
Ensure numbered pagination macro exists:
```html
{% macro numbered_pagination(page_var, total_var, limit_var, on_change) %}
{% endmacro %}
```
#### 4.2 Add Page Numbers Helper to JavaScript
**File:** `static/shared/js/helpers.js` or inline
```javascript
function getPageNumbers(current, total, maxVisible = 5) {
if (total <= maxVisible) {
return Array.from({length: total}, (_, i) => i + 1);
}
const half = Math.floor(maxVisible / 2);
let start = Math.max(1, current - half);
let end = Math.min(total, start + maxVisible - 1);
if (end - start < maxVisible - 1) {
start = Math.max(1, end - maxVisible + 1);
}
return Array.from({length: end - start + 1}, (_, i) => start + i);
}
```
#### 4.3 Update Each Table
Update each table to use the macro and consistent styling.
---
## 5. Platform-Wide Rows Per Page Setting
### Requirements
- Global setting for default rows per page
- Stored in platform settings (not per-user initially)
- Used by all paginated tables
- Options: 10, 20, 50, 100
### Implementation
#### 5.1 Add Platform Setting
**File:** `models/database/platform_settings.py` (create if doesn't exist)
```python
class PlatformSettings(Base):
__tablename__ = "platform_settings"
id = Column(Integer, primary_key=True)
key = Column(String(100), unique=True, nullable=False)
value = Column(String(500), nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow)
```
Or add to existing settings table if one exists.
#### 5.2 Create Settings Service
**File:** `app/services/platform_settings_service.py`
```python
class PlatformSettingsService:
def get_setting(self, db: Session, key: str, default: Any = None) -> Any:
setting = db.query(PlatformSettings).filter_by(key=key).first()
return setting.value if setting else default
def set_setting(self, db: Session, key: str, value: Any) -> None:
setting = db.query(PlatformSettings).filter_by(key=key).first()
if setting:
setting.value = str(value)
else:
setting = PlatformSettings(key=key, value=str(value))
db.add(setting)
db.flush()
def get_rows_per_page(self, db: Session) -> int:
return int(self.get_setting(db, "rows_per_page", "20"))
```
#### 5.3 Expose via API
**File:** `app/api/v1/admin/settings.py`
```python
@router.get("/platform/rows-per-page")
def get_rows_per_page(db: Session = Depends(get_db)):
return {"rows_per_page": platform_settings_service.get_rows_per_page(db)}
@router.put("/platform/rows-per-page")
def set_rows_per_page(
rows: int = Query(..., ge=10, le=100),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
platform_settings_service.set_setting(db, "rows_per_page", rows)
db.commit()
return {"rows_per_page": rows}
```
#### 5.4 Load Setting in Frontend
**File:** `static/shared/js/app.js` or similar
```javascript
// Load platform settings on app init
async function loadPlatformSettings() {
try {
const response = await apiClient.get('/admin/settings/platform/rows-per-page');
window.platformSettings = {
rowsPerPage: response.rows_per_page || 20
};
} catch {
window.platformSettings = { rowsPerPage: 20 };
}
}
```
#### 5.5 Use in Alpine Components
```javascript
// In each paginated component's init:
this.limit = window.platformSettings?.rowsPerPage || 20;
```
---
## Implementation Order
1. **Phase 1: Job Details Modal** (Quick win)
- Add modal template
- Update JS state and methods
- Test with export jobs
2. **Phase 2: Store Column** (Preparation)
- Update API response
- Update schema
- Add column to table
3. **Phase 3: Platform Settings** (Foundation)
- Create settings model/migration
- Create service
- Create API endpoint
- Frontend integration
4. **Phase 4: Table Harmonization** (Largest effort)
- Create/update table macros
- Add pagination helper function
- Update each table one by one
- Test thoroughly
5. **Phase 5: Documentation**
- Update component documentation
- Add settings documentation
---
## Files to Create/Modify
### New Files
- `models/database/platform_settings.py` (if not exists)
- `app/services/platform_settings_service.py`
- `alembic/versions/xxx_add_platform_settings.py`
### Modified Files
- `app/templates/admin/partials/letzshop-jobs-table.html`
- `app/templates/admin/partials/letzshop-products-tab.html`
- `app/templates/admin/partials/letzshop-orders-tab.html`
- `app/templates/admin/partials/letzshop-exceptions-tab.html`
- `app/templates/shared/macros/tables.html`
- `static/admin/js/marketplace-letzshop.js`
- `static/shared/js/helpers.js` or `app.js`
- `app/services/letzshop/order_service.py`
- `models/schema/letzshop.py`
- `app/api/v1/admin/settings.py` or new file
---
## 6. Admin Customer Page
### Requirements
- New page at `/admin/customers` to manage customers
- List all customers across stores
- Search and filter capabilities
- View customer details and order history
- Link to store context
### Implementation
#### 6.1 Database Model Check
**File:** `models/database/customer.py`
Verify Customer model exists with fields:
- id, store_id
- email, name, phone
- shipping address fields
- created_at, updated_at
#### 6.2 Create Customer Service
**File:** `app/services/customer_service.py`
```python
class CustomerService:
def get_customers(
self,
db: Session,
skip: int = 0,
limit: int = 20,
search: str | None = None,
store_id: int | None = None,
) -> tuple[list[dict], int]:
"""Get paginated customer list with optional filters."""
pass
def get_customer_detail(self, db: Session, customer_id: int) -> dict:
"""Get customer with order history."""
pass
def get_customer_stats(self, db: Session, store_id: int | None = None) -> dict:
"""Get customer statistics."""
pass
```
#### 6.3 Create API Endpoints
**File:** `app/api/v1/admin/customers.py`
```python
router = APIRouter(prefix="/customers")
@router.get("", response_model=CustomerListResponse)
def get_customers(
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
search: str | None = Query(None),
store_id: int | None = Query(None),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""List all customers with filtering."""
pass
@router.get("/stats", response_model=CustomerStatsResponse)
def get_customer_stats(...):
"""Get customer statistics."""
pass
@router.get("/{customer_id}", response_model=CustomerDetailResponse)
def get_customer_detail(...):
"""Get customer with order history."""
pass
```
#### 6.4 Create Pydantic Schemas
**File:** `models/schema/customer.py`
```python
class CustomerListItem(BaseModel):
id: int
email: str
name: str | None
phone: str | None
store_id: int
store_name: str | None
order_count: int
total_spent: float
created_at: datetime
class CustomerListResponse(BaseModel):
customers: list[CustomerListItem]
total: int
skip: int
limit: int
class CustomerDetailResponse(CustomerListItem):
shipping_address: str | None
orders: list[OrderSummary]
class CustomerStatsResponse(BaseModel):
total: int
new_this_month: int
active: int # ordered in last 90 days
by_store: dict[str, int]
```
#### 6.5 Create Admin Page Route
**File:** `app/routes/admin_pages.py`
```python
@router.get("/customers", response_class=HTMLResponse)
async def admin_customers_page(request: Request, ...):
return templates.TemplateResponse(
"admin/customers.html",
{"request": request, "current_page": "customers"}
)
```
#### 6.6 Create Template
**File:** `app/templates/admin/customers.html`
Structure:
- Page header with title and stats
- Search bar and filters (store dropdown)
- Customer table with pagination
- Click row to view details modal
#### 6.7 Create Alpine Component
**File:** `static/admin/js/customers.js`
```javascript
function adminCustomers() {
return {
customers: [],
total: 0,
page: 1,
limit: 20,
search: '',
storeFilter: '',
loading: false,
stats: {},
async init() {
await Promise.all([
this.loadCustomers(),
this.loadStats()
]);
},
async loadCustomers() { ... },
async loadStats() { ... },
async viewCustomer(id) { ... },
}
}
```
#### 6.8 Add to Sidebar
**File:** `app/templates/admin/partials/sidebar.html`
Add menu item:
```html
{{ menu_item('customers', '/admin/customers', 'users', 'Customers') }}
```
### Customer Page Features
| Feature | Description |
|---------|-------------|
| List View | Paginated table of all customers |
| Search | Search by name, email, phone |
| Store Filter | Filter by store |
| Stats Cards | Total, new, active customers |
| Detail Modal | Customer info + order history |
| Quick Actions | View orders, send email |
---
## Implementation Order
1. **Phase 1: Job Details Modal** (Quick win)
- Add modal template
- Update JS state and methods
- Test with export jobs
2. **Phase 2: Store Column** (Preparation)
- Update API response
- Update schema
- Add column to table
3. **Phase 3: Platform Settings** (Foundation)
- Create settings model/migration
- Create service
- Create API endpoint
- Frontend integration
4. **Phase 4: Table Harmonization** (Largest effort)
- Create/update table macros
- Add pagination helper function
- Update each table one by one
- Test thoroughly
5. **Phase 5: Admin Customer Page**
- Create service and API
- Create schemas
- Create template and JS
- Add to sidebar
6. **Phase 6: Documentation**
- Update component documentation
- Add settings documentation
- Add customer page documentation
---
## Files to Create/Modify
### New Files
- `models/database/platform_settings.py` (if not exists)
- `app/services/platform_settings_service.py`
- `app/services/customer_service.py`
- `app/api/v1/admin/customers.py`
- `models/schema/customer.py`
- `app/templates/admin/customers.html`
- `static/admin/js/customers.js`
- `alembic/versions/xxx_add_platform_settings.py`
### Modified Files
- `app/templates/admin/partials/letzshop-jobs-table.html`
- `app/templates/admin/partials/letzshop-products-tab.html`
- `app/templates/admin/partials/letzshop-orders-tab.html`
- `app/templates/admin/partials/letzshop-exceptions-tab.html`
- `app/templates/admin/partials/sidebar.html`
- `app/templates/shared/macros/tables.html`
- `static/admin/js/marketplace-letzshop.js`
- `static/shared/js/helpers.js` or `app.js`
- `app/services/letzshop/order_service.py`
- `models/schema/letzshop.py`
- `app/api/v1/admin/__init__.py`
- `app/routes/admin_pages.py`
---
## Estimated Effort
| Task | Effort |
|------|--------|
| Job Details Modal | Small |
| Tab Visibility (no change) | None |
| Store Column | Small |
| Platform Settings | Medium |
| Table Harmonization | Large |
| Admin Customer Page | Medium |
**Total:** Large effort
---
*Plan created: 2024-12-20*