fix_page
This commit is contained in:
parent
af769258d2
commit
8cbad66b32
@ -57,11 +57,11 @@ func main() {
|
||||
serviceTrialPeriods := make([]services.TrialPeriod, len(trialPeriods))
|
||||
for i, tp := range trialPeriods {
|
||||
serviceTrialPeriods[i] = services.TrialPeriod{
|
||||
ID: tp.ID,
|
||||
CustomerID: tp.CustomerID,
|
||||
StartTime: tp.StartTime,
|
||||
EndTime: tp.EndTime,
|
||||
CreatedAt: tp.CreatedAt,
|
||||
ID: tp.ID,
|
||||
CustomerName: tp.CustomerName,
|
||||
StartTime: tp.StartTime,
|
||||
EndTime: tp.EndTime,
|
||||
CreatedAt: tp.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,11 +97,11 @@ func main() {
|
||||
serviceTrialPeriods := make([]services.TrialPeriod, len(trialPeriods))
|
||||
for i, tp := range trialPeriods {
|
||||
serviceTrialPeriods[i] = services.TrialPeriod{
|
||||
ID: tp.ID,
|
||||
CustomerID: tp.CustomerID,
|
||||
StartTime: tp.StartTime,
|
||||
EndTime: tp.EndTime,
|
||||
CreatedAt: tp.CreatedAt,
|
||||
ID: tp.ID,
|
||||
CustomerName: tp.CustomerName,
|
||||
StartTime: tp.StartTime,
|
||||
EndTime: tp.EndTime,
|
||||
CreatedAt: tp.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -253,8 +253,7 @@
|
||||
|
||||
<!-- Trial Periods Section -->
|
||||
<section id="trialPeriodsSection" class="content-section">
|
||||
<div class="section-title">
|
||||
<h2><i class="fas fa-clock"></i> 客户信息</h2>
|
||||
<div class="section-title" style="justify-content: flex-end;">
|
||||
<button id="addTrialBtn" class="btn-primary">
|
||||
<i class="fas fa-plus"></i>
|
||||
添加试用时间
|
||||
|
||||
@ -1096,34 +1096,30 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const customersData = await customersResponse.json();
|
||||
const customersMap = customersData.customerMap || {};
|
||||
|
||||
// Collect unique trial customer names - only include valid customer names
|
||||
// Collect unique trial customer names - directly use customerName field
|
||||
const trialCustomerNames = new Set();
|
||||
trialPeriods.forEach(period => {
|
||||
// Only add if customerId exists in map (valid customer)
|
||||
const customerName = customersMap[period.customerId];
|
||||
if (customerName) {
|
||||
trialCustomerNames.add(customerName);
|
||||
// 直接使用 customerName 字段
|
||||
if (period.customerName) {
|
||||
trialCustomerNames.add(period.customerName);
|
||||
}
|
||||
// If customerId not in map, skip it (deleted customer) - don't add UUID as name
|
||||
});
|
||||
|
||||
// Update total customers to include trial customers
|
||||
// Update total customers display (数据仪表盘的总客户数从每周进度获取)
|
||||
// This function will be called after loading dashboard customers
|
||||
updateTotalCustomersWithTrialData(trialCustomerNames);
|
||||
} catch (error) {
|
||||
console.error('Error loading trial customers for dashboard:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update total customers count including trial customers
|
||||
// Update total customers count - 数据仪表盘的总客户数取的是每周进度表里的客户数量
|
||||
function updateTotalCustomersWithTrialData(trialCustomerNames) {
|
||||
// Get current dashboard customers
|
||||
// Get current dashboard customers (from customers.json / 每周进度)
|
||||
const progressCustomerNames = new Set(dashboardCustomers.map(c => c.customerName).filter(c => c));
|
||||
|
||||
// Merge both sets
|
||||
const allCustomerNames = new Set([...progressCustomerNames, ...trialCustomerNames]);
|
||||
|
||||
// Update the total customers display
|
||||
document.getElementById('totalCustomers').textContent = allCustomerNames.size;
|
||||
// 数据仪表盘的总客户数取的是每周进度表里的客户数量,支持去重
|
||||
document.getElementById('totalCustomers').textContent = progressCustomerNames.size;
|
||||
}
|
||||
|
||||
// Update dashboard statistics
|
||||
|
||||
@ -94,11 +94,31 @@ async function loadCustomersMap() {
|
||||
|
||||
// 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 = '<option value="">-- 选择已有客户或输入新客户 --</option>';
|
||||
|
||||
// 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 '未知客户';
|
||||
@ -182,7 +202,8 @@ function renderExpiryWarnings() {
|
||||
endTime.setHours(0, 0, 0, 0); // Set to start of day
|
||||
|
||||
const daysUntilExpiry = Math.ceil((endTime - now) / (1000 * 60 * 60 * 24));
|
||||
const customerName = getCustomerName(period.customerId);
|
||||
// 直接使用 customerName 字段
|
||||
const customerName = period.customerName || '未知客户';
|
||||
|
||||
if (daysUntilExpiry >= 0 && daysUntilExpiry <= 3) {
|
||||
warnings.push({
|
||||
@ -272,8 +293,8 @@ function renderTrialPeriodsTable() {
|
||||
const row = document.createElement('tr');
|
||||
row.classList.add('trial-row');
|
||||
|
||||
// 显示客户名称,如果找不到则显示ID
|
||||
const customerName = getCustomerName(period.customerId);
|
||||
// 直接使用 customerName 字段
|
||||
const customerName = period.customerName || '未知客户';
|
||||
|
||||
// 计算状态和到期天数
|
||||
const now = new Date();
|
||||
@ -379,7 +400,9 @@ function updateTrialPagination() {
|
||||
|
||||
// Open add trial modal
|
||||
function openAddTrialModal() {
|
||||
document.getElementById('trialCustomerInput').value = '';
|
||||
const inputEl = document.getElementById('trialCustomerInput');
|
||||
if (inputEl) inputEl.value = '';
|
||||
|
||||
document.querySelector('input[name="isTrial"][value="true"]').checked = true;
|
||||
document.getElementById('trialStartTime').value = '';
|
||||
document.getElementById('trialEndTime').value = '';
|
||||
@ -434,14 +457,18 @@ async function deleteTrialPeriodFromPage(periodId) {
|
||||
|
||||
// Create trial period from page
|
||||
async function createTrialPeriodFromPage() {
|
||||
const customerName = document.getElementById('trialCustomerInput').value.trim();
|
||||
const inputEl = document.getElementById('trialCustomerInput');
|
||||
|
||||
// Get customer name from input
|
||||
const customerName = inputEl ? inputEl.value.trim() : '';
|
||||
|
||||
const isTrialValue = document.querySelector('input[name="isTrial"]:checked').value;
|
||||
const isTrial = isTrialValue === 'true';
|
||||
const startTime = document.getElementById('trialStartTime').value;
|
||||
const endTime = document.getElementById('trialEndTime').value;
|
||||
|
||||
if (!customerName) {
|
||||
alert('请输入客户名称');
|
||||
alert('请选择或输入客户名称');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -450,22 +477,9 @@ async function createTrialPeriodFromPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find or create customer ID from customer name
|
||||
let customerId = null;
|
||||
for (const [id, name] of Object.entries(customersMap)) {
|
||||
if (name === customerName) {
|
||||
customerId = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If customer doesn't exist in map, generate a new ID
|
||||
if (!customerId) {
|
||||
customerId = customerName; // Use customer name as ID for now
|
||||
}
|
||||
|
||||
// 直接使用 customerName,不再需要查找或创建 customerId
|
||||
const formData = {
|
||||
customerId: customerId,
|
||||
customerName: customerName,
|
||||
startTime: new Date(startTime).toISOString(),
|
||||
endTime: new Date(endTime).toISOString(),
|
||||
isTrial: isTrial
|
||||
|
||||
@ -83,11 +83,11 @@ func (h *TrialPeriodHandler) CreateTrialPeriod(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
trialPeriod := models.TrialPeriod{
|
||||
CustomerID: req.CustomerID,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
IsTrial: req.IsTrial,
|
||||
CreatedAt: time.Now(),
|
||||
CustomerName: req.CustomerName,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
IsTrial: req.IsTrial,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
createdPeriod, err := h.storage.CreateTrialPeriod(trialPeriod)
|
||||
@ -98,7 +98,7 @@ func (h *TrialPeriodHandler) CreateTrialPeriod(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// Send Feishu notification
|
||||
if h.feishuWebhook != "" {
|
||||
go h.sendTrialNotification(req.CustomerID, startTime, endTime)
|
||||
go h.sendTrialNotification(req.CustomerName, startTime, endTime)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@ -152,7 +152,7 @@ func (h *TrialPeriodHandler) UpdateTrialPeriod(w http.ResponseWriter, r *http.Re
|
||||
} else {
|
||||
endTime = existingPeriod.EndTime
|
||||
}
|
||||
go h.sendTrialNotification(existingPeriod.CustomerID, startTime, endTime)
|
||||
go h.sendTrialNotification(existingPeriod.CustomerName, startTime, endTime)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@ -182,7 +182,7 @@ func (h *TrialPeriodHandler) DeleteTrialPeriod(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// sendTrialNotification sends a rich Feishu card notification
|
||||
func (h *TrialPeriodHandler) sendTrialNotification(customerID string, startTime, endTime time.Time) {
|
||||
func (h *TrialPeriodHandler) sendTrialNotification(customerName string, startTime, endTime time.Time) {
|
||||
if h.feishuWebhook == "" {
|
||||
return
|
||||
}
|
||||
@ -198,12 +198,8 @@ func (h *TrialPeriodHandler) sendTrialNotification(customerID string, startTime,
|
||||
return
|
||||
}
|
||||
|
||||
// Get customer name
|
||||
customerName := customerID
|
||||
customer, err := h.customerStorage.GetCustomerByID(customerID)
|
||||
if err == nil && customer != nil {
|
||||
customerName = customer.CustomerName
|
||||
}
|
||||
// customerName is already passed as parameter, no need to lookup
|
||||
// Just use it directly for notification
|
||||
|
||||
// Determine urgency level and message based on days until expiry
|
||||
var title, urgencyLevel, urgencyColor, expiryText string
|
||||
@ -324,41 +320,28 @@ func (h *TrialPeriodHandler) sendTrialNotification(customerID string, startTime,
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Sent trial notification for customer: %s (expires in %d days)", customerID, daysUntilExpiry)
|
||||
log.Printf("Sent trial notification for customer: %s (expires in %d days)", customerName, daysUntilExpiry)
|
||||
}
|
||||
|
||||
// GetTrialCustomerList returns a list of unique customer names from trial periods
|
||||
// GetTrialCustomerList returns a list of unique customer names from trial_periods
|
||||
// This is the data source for dropdown lists in 每周进度 and 客户跟进
|
||||
func (h *TrialPeriodHandler) GetTrialCustomerList(w http.ResponseWriter, r *http.Request) {
|
||||
// Get all trial periods
|
||||
trialPeriods, err := h.storage.GetAllTrialPeriods()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a set of unique customer IDs
|
||||
customerIDs := make(map[string]bool)
|
||||
for _, period := range trialPeriods {
|
||||
if period.CustomerID != "" {
|
||||
customerIDs[period.CustomerID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Get unique customer names (only include customers that exist in storage)
|
||||
// Get unique customer names from trial periods
|
||||
seenNames := make(map[string]bool)
|
||||
var customerNames []string
|
||||
|
||||
for customerID := range customerIDs {
|
||||
// Try to get customer name from customer storage
|
||||
customer, err := h.customerStorage.GetCustomerByID(customerID)
|
||||
if err == nil && customer != nil && customer.CustomerName != "" {
|
||||
customerName := customer.CustomerName
|
||||
// Add to list if not seen before
|
||||
if !seenNames[customerName] {
|
||||
customerNames = append(customerNames, customerName)
|
||||
seenNames[customerName] = true
|
||||
}
|
||||
for _, period := range trialPeriods {
|
||||
if period.CustomerName != "" && !seenNames[period.CustomerName] {
|
||||
customerNames = append(customerNames, period.CustomerName)
|
||||
seenNames[period.CustomerName] = true
|
||||
}
|
||||
// Skip customers that don't exist in storage (don't show ID as name)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
@ -64,7 +64,7 @@ func (ts *trialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string) ([]
|
||||
|
||||
var customerPeriods []models.TrialPeriod
|
||||
for _, period := range allPeriods {
|
||||
if period.CustomerID == customerID {
|
||||
if period.CustomerName == customerID {
|
||||
customerPeriods = append(customerPeriods, period)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,27 +2,28 @@ package models
|
||||
|
||||
import "time"
|
||||
|
||||
// TrialPeriod represents a trial period for a customer
|
||||
// TrialPeriod represents a trial period for a customer (客户信息)
|
||||
type TrialPeriod struct {
|
||||
ID string `json:"id"`
|
||||
CustomerID string `json:"customerId"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
IsTrial bool `json:"isTrial"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ID string `json:"id"`
|
||||
CustomerName string `json:"customerName"` // 直接存储客户名称
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
IsTrial bool `json:"isTrial"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// CreateTrialPeriodRequest represents the request to create a trial period
|
||||
type CreateTrialPeriodRequest struct {
|
||||
CustomerID string `json:"customerId"`
|
||||
StartTime string `json:"startTime"`
|
||||
EndTime string `json:"endTime"`
|
||||
IsTrial bool `json:"isTrial"`
|
||||
CustomerName string `json:"customerName"` // 直接使用客户名称
|
||||
StartTime string `json:"startTime"`
|
||||
EndTime string `json:"endTime"`
|
||||
IsTrial bool `json:"isTrial"`
|
||||
}
|
||||
|
||||
// UpdateTrialPeriodRequest represents the request to update a trial period
|
||||
type UpdateTrialPeriodRequest struct {
|
||||
StartTime *string `json:"startTime,omitempty"`
|
||||
EndTime *string `json:"endTime,omitempty"`
|
||||
IsTrial *bool `json:"isTrial,omitempty"`
|
||||
CustomerName *string `json:"customerName,omitempty"`
|
||||
StartTime *string `json:"startTime,omitempty"`
|
||||
EndTime *string `json:"endTime,omitempty"`
|
||||
IsTrial *bool `json:"isTrial,omitempty"`
|
||||
}
|
||||
|
||||
@ -40,11 +40,11 @@ type Customer struct {
|
||||
|
||||
// TrialPeriod represents a trial period record
|
||||
type TrialPeriod struct {
|
||||
ID string `json:"id"`
|
||||
CustomerID string `json:"customerId"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ID string `json:"id"`
|
||||
CustomerName string `json:"customerName"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime time.Time `json:"endTime"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// ParseTrialPeriod parses the trial period string
|
||||
@ -151,9 +151,9 @@ func (t *TrialExpiryChecker) CheckTrialPeriodsAndNotify(trialPeriods []TrialPeri
|
||||
|
||||
// Send notification for trials expiring today, tomorrow, or within 3 days
|
||||
if daysUntilExpiry >= 0 && daysUntilExpiry <= 3 {
|
||||
customerName := customersMap[period.CustomerID]
|
||||
customerName := period.CustomerName
|
||||
if customerName == "" {
|
||||
customerName = period.CustomerID
|
||||
customerName = "未知客户"
|
||||
}
|
||||
|
||||
trialPeriodStr := fmt.Sprintf("%s~%s",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user