Store detail page now shows all platform subscriptions instead of always "No Subscription Found". Subscriptions listing page renamed from Store to Merchant throughout (template, JS, menu, i18n) with Platform column added. Tiers API supports platform_id filtering. Merchant detail page no longer hardcodes 'oms' platform — loads all platforms, shows subscription cards per platform with labels, and the Create Subscription modal includes a platform selector with platform-filtered tiers. Create button always accessible in Quick Actions. Edit modal on /admin/subscriptions loads tiers from API filtered by platform instead of hardcoded options, sends tier_code (not tier) to match PATCH schema, and shows platform context. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
192 lines
7.2 KiB
JavaScript
192 lines
7.2 KiB
JavaScript
// noqa: js-006 - async init pattern is safe, loadData has try/catch
|
|
// static/admin/js/store-detail.js
|
|
|
|
// ✅ Use centralized logger - ONE LINE!
|
|
// Create custom logger for store detail
|
|
const detailLog = window.LogConfig.createLogger('STORE-DETAIL');
|
|
|
|
function adminStoreDetail() {
|
|
return {
|
|
// Inherit base layout functionality from init-alpine.js
|
|
...data(),
|
|
|
|
// Store detail page specific state
|
|
currentPage: 'store-detail',
|
|
store: null,
|
|
subscriptions: [],
|
|
loading: false,
|
|
error: null,
|
|
storeCode: null,
|
|
|
|
// Initialize
|
|
async init() {
|
|
// Load i18n translations
|
|
await I18n.loadModule('tenancy');
|
|
|
|
detailLog.info('=== STORE DETAIL PAGE INITIALIZING ===');
|
|
|
|
// Prevent multiple initializations
|
|
if (window._storeDetailInitialized) {
|
|
detailLog.warn('Store detail page already initialized, skipping...');
|
|
return;
|
|
}
|
|
window._storeDetailInitialized = true;
|
|
|
|
// Get store code from URL
|
|
const path = window.location.pathname;
|
|
const match = path.match(/\/admin\/stores\/([^\/]+)$/);
|
|
|
|
if (match) {
|
|
this.storeCode = match[1];
|
|
detailLog.info('Viewing store:', this.storeCode);
|
|
await this.loadStore();
|
|
// Load subscription after store is loaded
|
|
if (this.store?.id) {
|
|
await this.loadSubscriptions();
|
|
}
|
|
} else {
|
|
detailLog.error('No store code in URL');
|
|
this.error = 'Invalid store URL';
|
|
Utils.showToast(I18n.t('tenancy.messages.invalid_store_url'), 'error');
|
|
}
|
|
|
|
detailLog.info('=== STORE DETAIL PAGE INITIALIZATION COMPLETE ===');
|
|
},
|
|
|
|
// Load store data
|
|
async loadStore() {
|
|
detailLog.info('Loading store details...');
|
|
this.loading = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
const url = `/admin/stores/${this.storeCode}`;
|
|
window.LogConfig.logApiCall('GET', url, null, 'request');
|
|
|
|
const startTime = performance.now();
|
|
const response = await apiClient.get(url);
|
|
const duration = performance.now() - startTime;
|
|
|
|
window.LogConfig.logApiCall('GET', url, response, 'response');
|
|
window.LogConfig.logPerformance('Load Store Details', duration);
|
|
|
|
this.store = response;
|
|
|
|
detailLog.info(`Store loaded in ${duration}ms`, {
|
|
store_code: this.store.store_code,
|
|
name: this.store.name,
|
|
is_verified: this.store.is_verified,
|
|
is_active: this.store.is_active
|
|
});
|
|
detailLog.debug('Full store data:', this.store);
|
|
|
|
} catch (error) {
|
|
window.LogConfig.logError(error, 'Load Store Details');
|
|
this.error = error.message || 'Failed to load store details';
|
|
Utils.showToast(I18n.t('tenancy.messages.failed_to_load_store_details'), 'error');
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
// Format date (matches dashboard pattern)
|
|
formatDate(dateString) {
|
|
if (!dateString) {
|
|
detailLog.debug('formatDate called with empty dateString');
|
|
return '-';
|
|
}
|
|
const formatted = Utils.formatDate(dateString);
|
|
detailLog.debug(`Date formatted: ${dateString} -> ${formatted}`);
|
|
return formatted;
|
|
},
|
|
|
|
// Load subscriptions data for this store via convenience endpoint
|
|
async loadSubscriptions() {
|
|
if (!this.store?.id) {
|
|
detailLog.warn('Cannot load subscriptions: no store ID');
|
|
return;
|
|
}
|
|
|
|
detailLog.info('Loading subscriptions for store:', this.store.id);
|
|
|
|
try {
|
|
const url = `/admin/subscriptions/store/${this.store.id}`;
|
|
window.LogConfig.logApiCall('GET', url, null, 'request');
|
|
|
|
const response = await apiClient.get(url);
|
|
window.LogConfig.logApiCall('GET', url, response, 'response');
|
|
|
|
this.subscriptions = response.subscriptions || [];
|
|
|
|
detailLog.info('Subscriptions loaded:', {
|
|
count: this.subscriptions.length,
|
|
platforms: this.subscriptions.map(e => e.platform_name)
|
|
});
|
|
|
|
} catch (error) {
|
|
// 404 means no subscription exists - that's OK
|
|
if (error.status === 404) {
|
|
detailLog.info('No subscriptions found for store');
|
|
this.subscriptions = [];
|
|
} else {
|
|
detailLog.warn('Failed to load subscriptions:', error.message);
|
|
}
|
|
}
|
|
},
|
|
|
|
// Get usage bar color based on percentage
|
|
getUsageBarColor(current, limit) {
|
|
if (!limit || limit === 0) return 'bg-blue-500';
|
|
const percent = (current / limit) * 100;
|
|
if (percent >= 90) return 'bg-red-500';
|
|
if (percent >= 75) return 'bg-yellow-500';
|
|
return 'bg-green-500';
|
|
},
|
|
|
|
// Delete store
|
|
async deleteStore() {
|
|
detailLog.info('Delete store requested:', this.storeCode);
|
|
|
|
if (!confirm(`Are you sure you want to delete store "${this.store.name}"?\n\nThis action cannot be undone and will delete:\n- All products\n- All orders\n- All customers\n- All team members`)) {
|
|
detailLog.info('Delete cancelled by user');
|
|
return;
|
|
}
|
|
|
|
// Second confirmation for safety
|
|
if (!confirm(`FINAL CONFIRMATION\n\nType the store code to confirm: ${this.store.store_code}\n\nAre you absolutely sure?`)) {
|
|
detailLog.info('Delete cancelled by user (second confirmation)');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const url = `/admin/stores/${this.storeCode}?confirm=true`;
|
|
window.LogConfig.logApiCall('DELETE', url, null, 'request');
|
|
|
|
detailLog.info('Deleting store:', this.storeCode);
|
|
await apiClient.delete(url);
|
|
|
|
window.LogConfig.logApiCall('DELETE', url, null, 'response');
|
|
|
|
Utils.showToast(I18n.t('tenancy.messages.store_deleted_successfully'), 'success');
|
|
detailLog.info('Store deleted successfully');
|
|
|
|
// Redirect to stores list
|
|
setTimeout(() => window.location.href = '/admin/stores', 1500);
|
|
|
|
} catch (error) {
|
|
window.LogConfig.logError(error, 'Delete Store');
|
|
Utils.showToast(error.message || 'Failed to delete store', 'error');
|
|
}
|
|
},
|
|
|
|
// Refresh store data
|
|
async refresh() {
|
|
detailLog.info('=== STORE REFRESH TRIGGERED ===');
|
|
await this.loadStore();
|
|
Utils.showToast(I18n.t('tenancy.messages.store_details_refreshed'), 'success');
|
|
detailLog.info('=== STORE REFRESH COMPLETE ===');
|
|
}
|
|
};
|
|
}
|
|
|
|
detailLog.info('Store detail module loaded'); |