// Trial Periods Page Management
// This file handles the standalone trial periods page
let trialPeriodsData = [];
let filteredTrialPeriodsData = [];
let trialCurrentPage = 1;
let trialPageSize = 10;
let trialTotalItems = 0;
let trialTotalPages = 0;
let customersMap = {}; // Map of customer ID to customer name
let trialStartDateFilter = '';
let trialEndDateFilter = '';
let trialSortOrder = 'desc';
// Initialize trial periods page
function initTrialPeriodsPage() {
const addTrialBtn = document.getElementById('addTrialBtn');
const trialPeriodsSection = document.getElementById('trialPeriodsSection');
if (!trialPeriodsSection) return;
// Add trial button click
if (addTrialBtn) {
addTrialBtn.addEventListener('click', function () {
openAddTrialModal();
});
}
// Pagination controls
document.getElementById('trialFirstPage')?.addEventListener('click', () => {
trialCurrentPage = 1;
applyTrialFiltersAndSort();
});
document.getElementById('trialPrevPage')?.addEventListener('click', () => {
if (trialCurrentPage > 1) {
trialCurrentPage--;
applyTrialFiltersAndSort();
}
});
document.getElementById('trialNextPage')?.addEventListener('click', () => {
if (trialCurrentPage < trialTotalPages) {
trialCurrentPage++;
applyTrialFiltersAndSort();
}
});
document.getElementById('trialLastPage')?.addEventListener('click', () => {
trialCurrentPage = trialTotalPages;
applyTrialFiltersAndSort();
});
document.getElementById('trialPageSizeSelect')?.addEventListener('change', function () {
trialPageSize = parseInt(this.value);
trialCurrentPage = 1;
applyTrialFiltersAndSort();
});
// Filter and sort event listeners
document.getElementById('trialStartDateFilter')?.addEventListener('change', function () {
trialStartDateFilter = this.value;
trialCurrentPage = 1;
applyTrialFiltersAndSort();
});
document.getElementById('trialEndDateFilter')?.addEventListener('change', function () {
trialEndDateFilter = this.value;
trialCurrentPage = 1;
applyTrialFiltersAndSort();
});
document.getElementById('trialSortOrder')?.addEventListener('change', function () {
trialSortOrder = this.value;
trialCurrentPage = 1;
applyTrialFiltersAndSort();
});
// Intended product checkbox change handlers
setupIntendedProductCheckboxes('trialIntendedProductOtherCheckbox', 'trialIntendedProductOther');
setupIntendedProductCheckboxes('editTrialIntendedProductOtherCheckbox', 'editTrialIntendedProductOther');
// Load customers map for displaying customer names
loadCustomersMap();
}
// Load customers map for displaying customer names in table
async function loadCustomersMap() {
try {
const response = await authenticatedFetch('/api/customers/list');
if (!response) {
console.error('No response from API');
return;
}
const data = await response.json();
// Use the complete customer map for ID->Name lookups
customersMap = data.customerMap || {};
// Also populate the customer select dropdown
populateCustomerSelect();
} catch (error) {
console.error('Error loading customers map:', error);
}
}
// Populate the customer select dropdown
function populateCustomerSelect() {
const select = document.getElementById('trialCustomerSelect');
if (!select) return;
select.innerHTML = '';
// Get unique names from customersMap
const names = [...new Set(Object.values(customersMap))].sort();
names.forEach(name => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
});
}
// Helper function to get customer name - returns friendly placeholder if not found
function getCustomerName(customerId) {
if (!customerId) return '未知客户';
const name = customersMap[customerId];
if (name) return name;
// If ID not found in map, return shortened ID with indicator
return '(已删除客户)';
}
// Alias for loadCustomersMap - used by main.js when switching to trial periods section
async function loadCustomersForDropdown() {
return await loadCustomersMap();
}
// Setup intended product checkboxes with "other" option handling
function setupIntendedProductCheckboxes(otherCheckboxId, otherInputId) {
const otherCheckbox = document.getElementById(otherCheckboxId);
const otherInput = document.getElementById(otherInputId);
if (!otherCheckbox || !otherInput) return;
otherCheckbox.addEventListener('change', function () {
if (this.checked) {
otherInput.style.display = 'block';
otherInput.focus();
} else {
otherInput.style.display = 'none';
otherInput.value = '';
}
});
}
// Get intended product values from checkboxes (returns comma-separated string)
function getIntendedProductValues(checkboxName, otherInputId) {
const checkboxes = document.querySelectorAll(`input[name="${checkboxName}"]:checked`);
const otherInput = document.getElementById(otherInputId);
const values = [];
checkboxes.forEach(cb => {
if (cb.value === '其他') {
// If "other" is checked, use the custom input value
if (otherInput && otherInput.value.trim()) {
values.push(otherInput.value.trim());
}
} else {
values.push(cb.value);
}
});
return values.join(', ');
}
// Set intended product values in checkboxes (accepts comma-separated string)
function setIntendedProductValues(checkboxName, otherInputId, value) {
const checkboxes = document.querySelectorAll(`input[name="${checkboxName}"]`);
const otherInput = document.getElementById(otherInputId);
// Reset all checkboxes
checkboxes.forEach(cb => {
cb.checked = false;
});
if (otherInput) {
otherInput.style.display = 'none';
otherInput.value = '';
}
if (!value) return;
// Parse comma-separated values
const selectedValues = value.split(',').map(v => v.trim()).filter(v => v);
const predefinedOptions = ['数据闭环', 'robogo'];
let hasOther = false;
const otherValues = [];
selectedValues.forEach(val => {
if (predefinedOptions.includes(val)) {
// Check the corresponding checkbox
checkboxes.forEach(cb => {
if (cb.value === val) {
cb.checked = true;
}
});
} else {
// Custom value - mark as "other"
hasOther = true;
otherValues.push(val);
}
});
// Handle "other" checkbox and input
if (hasOther) {
checkboxes.forEach(cb => {
if (cb.value === '其他') {
cb.checked = true;
}
});
if (otherInput) {
otherInput.style.display = 'block';
otherInput.value = otherValues.join(', ');
}
}
}
// Legacy compatibility functions (for backward compatibility)
function setupIntendedProductDropdown(selectId, otherId) {
// No-op for backward compatibility - now using checkbox groups
}
function getIntendedProductValue(selectId, otherId) {
// Map to new checkbox-based function based on the select ID
if (selectId === 'trialIntendedProduct') {
return getIntendedProductValues('intendedProduct', 'trialIntendedProductOther');
} else if (selectId === 'editTrialIntendedProduct') {
return getIntendedProductValues('editIntendedProduct', 'editTrialIntendedProductOther');
}
return '';
}
function setIntendedProductValue(selectId, otherId, value) {
// Map to new checkbox-based function based on the select ID
if (selectId === 'trialIntendedProduct') {
setIntendedProductValues('intendedProduct', 'trialIntendedProductOther', value);
} else if (selectId === 'editTrialIntendedProduct') {
setIntendedProductValues('editIntendedProduct', 'editTrialIntendedProductOther', value);
}
}
// Load all trial periods
async function loadAllTrialPeriods() {
try {
const response = await authenticatedFetch('/api/trial-periods/all');
const data = await response.json();
trialPeriodsData = data.trialPeriods || [];
applyTrialFiltersAndSort();
renderExpiryWarnings();
} catch (error) {
console.error('Error loading trial periods:', error);
trialPeriodsData = [];
filteredTrialPeriodsData = [];
renderTrialPeriodsTable();
}
}
// Apply filters and sorting to trial periods
function applyTrialFiltersAndSort() {
let filtered = [...trialPeriodsData];
// Apply date range filter
if (trialStartDateFilter) {
filtered = filtered.filter(period => {
const endDate = new Date(period.endTime).toISOString().split('T')[0];
return endDate >= trialStartDateFilter;
});
}
if (trialEndDateFilter) {
filtered = filtered.filter(period => {
const endDate = new Date(period.endTime).toISOString().split('T')[0];
return endDate <= trialEndDateFilter;
});
}
// Sort by end time
filtered.sort((a, b) => {
const dateA = new Date(a.endTime);
const dateB = new Date(b.endTime);
return trialSortOrder === 'asc' ? dateA - dateB : dateB - dateA;
});
filteredTrialPeriodsData = filtered;
trialTotalItems = filteredTrialPeriodsData.length;
trialTotalPages = Math.ceil(trialTotalItems / trialPageSize);
renderTrialPeriodsTable();
updateTrialPagination();
}
// Render expiry warning cards
function renderExpiryWarnings() {
const warningsContainer = document.getElementById('trialExpiryWarnings');
if (!warningsContainer) return;
warningsContainer.innerHTML = '';
const now = new Date();
now.setHours(0, 0, 0, 0); // Set to start of today
const warnings = [];
trialPeriodsData.forEach(period => {
const endTime = new Date(period.endTime);
endTime.setHours(0, 0, 0, 0); // Set to start of day
const daysUntilExpiry = Math.ceil((endTime - now) / (1000 * 60 * 60 * 24));
// 直接使用 customerName 字段
const customerName = period.customerName || '未知客户';
if (daysUntilExpiry >= 0 && daysUntilExpiry <= 3) {
warnings.push({
customerName,
endTime: period.endTime,
daysUntilExpiry,
period
});
}
});
// Sort by days until expiry (most urgent first)
warnings.sort((a, b) => a.daysUntilExpiry - b.daysUntilExpiry);
warnings.forEach(warning => {
const card = document.createElement('div');
let warningClass = 'warning-soon';
let iconClass = 'fa-info-circle';
let title = '试用即将到期';
let message = '';
if (warning.daysUntilExpiry === 0) {
warningClass = 'warning-today';
iconClass = 'fa-exclamation-triangle';
title = '试用今日到期';
message = `${warning.customerName} 客户的试用期将于今天到期,请及时跟进!`;
} else if (warning.daysUntilExpiry === 1) {
warningClass = 'warning-tomorrow';
iconClass = 'fa-exclamation-circle';
title = '试用明日到期';
message = `${warning.customerName} 客户的试用期将于明天到期,请及时跟进!`;
} else {
warningClass = 'warning-soon';
iconClass = 'fa-info-circle';
title = '试用即将到期';
message = `${warning.customerName} 客户的试用期将于${warning.daysUntilExpiry}天后到期,请及时跟进!`;
}
const formattedEndTime = formatDateTime(warning.endTime);
message += `
试用结束时间:${formattedEndTime}`;
card.className = `expiry-warning-card ${warningClass}`;
card.innerHTML = `
点击上方「添加客户」开始管理客户试用