feat: Add new server binaries and reorder 'Time' column in customer table and CSV export.

This commit is contained in:
hangyu.tao 2026-01-05 20:09:33 +08:00
parent 9e8c8ec8f2
commit 2b50b3a2f7
3 changed files with 193 additions and 179 deletions

View File

@ -107,7 +107,6 @@
<table id="customerTable">
<thead>
<tr>
<th>时间</th>
<th>客户</th>
<th>版本</th>
<th>描述</th>
@ -116,6 +115,7 @@
<th>模块</th>
<th>状态与进度</th>
<th>报告人</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>

View File

@ -1,4 +1,4 @@
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
// 登录守卫
const token = localStorage.getItem('crmToken');
if (!token && !window.location.pathname.endsWith('login.html')) {
@ -15,13 +15,13 @@ document.addEventListener('DOMContentLoaded', function() {
};
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
localStorage.removeItem('crmToken');
window.location.href = '/static/login.html';
return;
}
return response;
}
@ -30,7 +30,7 @@ document.addEventListener('DOMContentLoaded', function() {
const customerSection = document.getElementById('customerSection');
const dashboardSection = document.getElementById('dashboardSection');
const pageTitle = document.getElementById('pageTitle');
// Elements
const createCustomerForm = document.getElementById('createCustomerForm');
const importFileForm = document.getElementById('importFileForm');
@ -48,18 +48,18 @@ document.addEventListener('DOMContentLoaded', function() {
const contentArea = document.querySelector('.content-area');
const refreshCustomersBtn = document.getElementById('refreshCustomersBtn');
const exportCustomersBtn = document.getElementById('exportCustomersBtn');
let allCustomers = [];
let filteredCustomers = [];
let dashboardCustomers = [];
// Chart instances
let statusChartInstance = null;
let typeChartInstance = null;
// Current section tracking
let currentSection = 'customer';
// Pagination state
let currentPage = 1;
let pageSize = 10;
@ -198,88 +198,88 @@ document.addEventListener('DOMContentLoaded', function() {
const day = parts[2].padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Navigation event listeners
navItems.forEach(item => {
item.addEventListener('click', function(e) {
item.addEventListener('click', function (e) {
e.preventDefault();
const section = this.getAttribute('data-section');
switchSection(section);
});
});
// Menu toggle for mobile
menuToggle.addEventListener('click', function() {
menuToggle.addEventListener('click', function () {
sidebar.classList.toggle('open');
});
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(e) {
document.addEventListener('click', function (e) {
if (window.innerWidth <= 768) {
if (!sidebar.contains(e.target) && !menuToggle.contains(e.target)) {
sidebar.classList.remove('open');
}
}
});
// Add Customer button
addCustomerBtn.addEventListener('click', function() {
addCustomerBtn.addEventListener('click', function () {
createModal.style.display = 'block';
});
// Import button
importBtn.addEventListener('click', function() {
importBtn.addEventListener('click', function () {
importModal.style.display = 'block';
});
// Close create modal
createModal.querySelector('.close').addEventListener('click', function() {
createModal.querySelector('.close').addEventListener('click', function () {
createModal.style.display = 'none';
});
createModal.querySelector('.cancel-create').addEventListener('click', function() {
createModal.querySelector('.cancel-create').addEventListener('click', function () {
createModal.style.display = 'none';
});
// Close import modal
importModal.querySelector('.close').addEventListener('click', function() {
importModal.querySelector('.close').addEventListener('click', function () {
importModal.style.display = 'none';
});
importModal.querySelector('.cancel-import').addEventListener('click', function() {
importModal.querySelector('.cancel-import').addEventListener('click', function () {
importModal.style.display = 'none';
});
// Close edit modal
editModal.querySelector('.close').addEventListener('click', function() {
editModal.querySelector('.close').addEventListener('click', function () {
editModal.style.display = 'none';
});
editModal.querySelector('.cancel-edit').addEventListener('click', function() {
editModal.querySelector('.cancel-edit').addEventListener('click', function () {
editModal.style.display = 'none';
});
// File name display
const importFile = document.getElementById('importFile');
const fileName = document.getElementById('fileName');
importFile.addEventListener('change', function() {
importFile.addEventListener('change', function () {
if (this.files.length > 0) {
fileName.textContent = this.files[0].name;
} else {
fileName.textContent = '选择文件...';
}
});
// Customer filter change event
customerFilter.addEventListener('change', function() {
customerFilter.addEventListener('change', function () {
selectedCustomerFilter = this.value;
currentPage = 1;
applyAllCustomerFilters();
});
if (customerSearchInput) {
customerSearchInput.addEventListener('input', function() {
customerSearchInput.addEventListener('input', function () {
customerSearchQuery = (this.value || '').trim();
if (currentSection === 'customer') {
currentPage = 1;
@ -287,29 +287,29 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
}
// Apply date filter for dashboard
document.getElementById('applyFilters').addEventListener('click', function() {
document.getElementById('applyFilters').addEventListener('click', function () {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate);
});
// Chart title change events
document.getElementById('statusChartTitle').addEventListener('input', function() {
document.getElementById('statusChartTitle').addEventListener('input', function () {
applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value);
});
document.getElementById('typeChartTitle').addEventListener('input', function() {
document.getElementById('typeChartTitle').addEventListener('input', function () {
applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value);
});
// Switch between sections
function switchSection(section) {
navItems.forEach(item => {
item.classList.remove('active');
});
if (section === 'customer') {
customerSection.classList.add('active');
dashboardSection.classList.remove('active');
@ -325,19 +325,19 @@ document.addEventListener('DOMContentLoaded', function() {
currentSection = 'dashboard';
loadDashboardData();
}
// Close sidebar on mobile after navigation
if (window.innerWidth <= 768) {
sidebar.classList.remove('open');
}
}
// Load customers from API
async function loadCustomers() {
try {
const response = await authenticatedFetch('/api/customers?page=1&pageSize=1000');
const data = await response.json();
if (data.customers) {
allCustomers = data.customers;
populateCustomerFilter();
@ -347,14 +347,14 @@ document.addEventListener('DOMContentLoaded', function() {
console.error('Error loading customers:', error);
}
}
// Populate customer filter dropdown
function populateCustomerFilter() {
console.log('Populating filter, allCustomers:', allCustomers);
const uniqueCustomers = [...new Set(allCustomers.map(c => c.intendedProduct).filter(c => c))];
console.log('Unique customers:', uniqueCustomers);
customerFilter.innerHTML = '<option value="">全部客户</option>';
uniqueCustomers.forEach(customer => {
const option = document.createElement('option');
option.value = customer;
@ -366,7 +366,7 @@ document.addEventListener('DOMContentLoaded', function() {
customerFilter.value = selectedCustomerFilter;
}
}
// Filter customers by selected customer
function filterCustomers(selectedCustomer) {
selectedCustomerFilter = selectedCustomer;
@ -438,7 +438,6 @@ document.addEventListener('DOMContentLoaded', function() {
function exportCustomersToCsv(customers) {
const header = [
'时间',
'客户',
'版本',
'描述',
@ -446,13 +445,13 @@ document.addEventListener('DOMContentLoaded', function() {
'类型',
'模块',
'状态与进度',
'报告人'
'报告人',
'时间'
].map(toCsvCell).join(',');
const lines = customers.map(c => {
const date = normalizeDateValue(c.customerName) || (c.customerName || '');
const cells = [
date,
c.intendedProduct || '',
c.version || '',
c.description || '',
@ -460,7 +459,8 @@ document.addEventListener('DOMContentLoaded', function() {
c.type || '',
c.module || '',
c.statusProgress || '',
c.reporter || ''
c.reporter || '',
date
];
return cells.map(toCsvCell).join(',');
});
@ -491,29 +491,29 @@ document.addEventListener('DOMContentLoaded', function() {
exportCustomersToCsv(filteredCustomers);
});
}
// Apply customer filter and render table with pagination
function applyCustomerFilter() {
totalItems = filteredCustomers.length;
totalPages = Math.ceil(totalItems / pageSize);
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedCustomers = filteredCustomers.slice(startIndex, endIndex);
renderCustomerTable(paginatedCustomers);
updatePaginationControls();
}
// Load all customers for dashboard
async function loadAllCustomers() {
console.log('loadAllCustomers called');
try {
const response = await authenticatedFetch('/api/customers?page=1&pageSize=1000');
const data = await response.json();
console.log('Dashboard data received:', data);
if (data.customers) {
dashboardCustomers = data.customers;
console.log('Dashboard customers set:', dashboardCustomers.length, 'customers');
@ -527,11 +527,11 @@ document.addEventListener('DOMContentLoaded', function() {
console.error('Error loading all customers:', error);
}
}
// Apply date filter for dashboard
function applyDateFilter(startDate, endDate) {
let filteredData = dashboardCustomers;
if (startDate) {
filteredData = filteredData.filter(c => {
if (!c.customerName) return false;
@ -539,7 +539,7 @@ document.addEventListener('DOMContentLoaded', function() {
return date && date >= startDate;
});
}
if (endDate) {
filteredData = filteredData.filter(c => {
if (!c.customerName) return false;
@ -547,23 +547,22 @@ document.addEventListener('DOMContentLoaded', function() {
return date && date <= endDate;
});
}
updateDashboardStats(filteredData);
renderStatusChart(filteredData);
renderTypeChart(filteredData);
}
// Render customer table
function renderCustomerTable(customers) {
customerTableBody.innerHTML = '';
customers.forEach(customer => {
const row = document.createElement('tr');
const date = customer.customerName || '';
const fields = [
{ value: date, name: 'date' },
{ value: customer.intendedProduct || '', name: 'intendedProduct' },
{ value: customer.version || '', name: 'version' },
{ value: customer.description || '', name: 'description' },
@ -571,22 +570,23 @@ document.addEventListener('DOMContentLoaded', function() {
{ value: customer.type || '', name: 'type' },
{ value: customer.module || '', name: 'module' },
{ value: customer.statusProgress || '', name: 'statusProgress' },
{ value: customer.reporter || '', name: 'reporter' }
{ value: customer.reporter || '', name: 'reporter' },
{ value: date, name: 'date' }
];
fields.forEach(field => {
const td = document.createElement('td');
const textValue = String(field.value ?? '');
td.textContent = textValue;
td.setAttribute('data-tooltip', textValue);
if (field.name === 'description' || field.name === 'solution') {
td.classList.add('overflow-cell');
}
row.appendChild(td);
});
const actionTd = document.createElement('td');
actionTd.innerHTML = `
<button class="action-btn edit-btn" data-id="${customer.id}">
@ -597,27 +597,27 @@ document.addEventListener('DOMContentLoaded', function() {
</button>
`;
row.appendChild(actionTd);
customerTableBody.appendChild(row);
});
checkTextOverflow();
document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function() {
btn.addEventListener('click', function () {
const customerId = this.getAttribute('data-id');
openEditModal(customerId);
});
});
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function() {
btn.addEventListener('click', function () {
const customerId = this.getAttribute('data-id');
deleteCustomer(customerId);
});
});
}
function checkTextOverflow() {
// Use requestAnimationFrame to ensure DOM is fully rendered before checking overflow
requestAnimationFrame(() => {
@ -635,31 +635,31 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
}
// Update pagination controls
function updatePaginationControls() {
const startItem = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const endItem = Math.min(currentPage * pageSize, totalItems);
document.getElementById('paginationInfo').textContent =
document.getElementById('paginationInfo').textContent =
`显示 ${startItem}-${endItem}${totalItems}`;
document.getElementById('firstPage').disabled = currentPage === 1;
document.getElementById('prevPage').disabled = currentPage === 1;
document.getElementById('nextPage').disabled = currentPage === totalPages;
document.getElementById('lastPage').disabled = currentPage === totalPages;
const pageNumbers = document.getElementById('pageNumbers');
pageNumbers.innerHTML = '';
const maxVisiblePages = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
if (endPage - startPage + 1 < maxVisiblePages) {
startPage = Math.max(1, endPage - maxVisiblePages + 1);
}
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `page-number ${i === currentPage ? 'active' : ''}`;
@ -671,11 +671,11 @@ document.addEventListener('DOMContentLoaded', function() {
pageNumbers.appendChild(pageBtn);
}
}
// Create customer
createCustomerForm.addEventListener('submit', async function(e) {
createCustomerForm.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = {
customerName: document.getElementById('customerName').value,
intendedProduct: document.getElementById('intendedProduct').value,
@ -687,7 +687,7 @@ document.addEventListener('DOMContentLoaded', function() {
statusProgress: document.getElementById('createStatusProgress').value,
reporter: document.getElementById('createReporter').value
};
try {
const response = await authenticatedFetch('/api/customers', {
method: 'POST',
@ -696,7 +696,7 @@ document.addEventListener('DOMContentLoaded', function() {
},
body: JSON.stringify(formData)
});
if (response.ok) {
createCustomerForm.reset();
createModal.style.display = 'none';
@ -710,27 +710,27 @@ document.addEventListener('DOMContentLoaded', function() {
alert('创建客户时出错');
}
});
// Import customers
importFileForm.addEventListener('submit', async function(e) {
importFileForm.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById('importFile');
if (!fileInput.files.length) {
alert('请选择要导入的文件');
return;
}
formData.append('file', fileInput.files[0]);
try {
const response = await authenticatedFetch('/api/customers/import', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
const message = `客户导入成功!\n导入数量: ${result.importedCount}\n重复数量: ${result.duplicateCount}`;
@ -747,13 +747,13 @@ document.addEventListener('DOMContentLoaded', function() {
alert('导入客户时出错');
}
});
// Open edit modal
async function openEditModal(customerId) {
try {
const response = await authenticatedFetch(`/api/customers/${customerId}`);
const customer = await response.json();
document.getElementById('editCustomerId').value = customer.id;
document.getElementById('editCustomerName').value = normalizeDateValue(customer.customerName);
document.getElementById('editIntendedProduct').value = customer.intendedProduct || '';
@ -764,14 +764,14 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('editModule').value = customer.module || '';
document.getElementById('editStatusProgress').value = customer.statusProgress || '';
document.getElementById('editReporter').value = customer.reporter || '';
editModal.style.display = 'block';
} catch (error) {
console.error('Error loading customer for edit:', error);
}
}
window.addEventListener('click', function(e) {
window.addEventListener('click', function (e) {
if (e.target === createModal) {
createModal.style.display = 'none';
}
@ -782,13 +782,13 @@ document.addEventListener('DOMContentLoaded', function() {
editModal.style.display = 'none';
}
});
// Update customer
editCustomerForm.addEventListener('submit', async function(e) {
editCustomerForm.addEventListener('submit', async function (e) {
e.preventDefault();
const customerId = document.getElementById('editCustomerId').value;
const formData = {
customerName: document.getElementById('editCustomerName').value,
intendedProduct: document.getElementById('editIntendedProduct').value,
@ -800,7 +800,7 @@ document.addEventListener('DOMContentLoaded', function() {
statusProgress: document.getElementById('editStatusProgress').value,
reporter: document.getElementById('editReporter').value
};
try {
const response = await authenticatedFetch(`/api/customers/${customerId}`, {
method: 'PUT',
@ -809,7 +809,7 @@ document.addEventListener('DOMContentLoaded', function() {
},
body: JSON.stringify(formData)
});
if (response.ok) {
editModal.style.display = 'none';
loadCustomers();
@ -822,18 +822,18 @@ document.addEventListener('DOMContentLoaded', function() {
alert('更新客户时出错');
}
});
// Delete customer
async function deleteCustomer(customerId) {
if (!confirm('确定要删除这个客户吗?')) {
return;
}
try {
const response = await authenticatedFetch(`/api/customers/${customerId}`, {
method: 'DELETE'
});
if (response.ok) {
loadCustomers();
alert('客户删除成功!');
@ -845,17 +845,17 @@ document.addEventListener('DOMContentLoaded', function() {
alert('删除客户时出错');
}
}
// Dashboard functionality
async function loadDashboardData() {
console.log('loadDashboardData called');
await loadAllCustomers();
}
// Update dashboard statistics
function updateDashboardStats(customers) {
const totalCustomers = new Set(customers.map(c => c.intendedProduct).filter(c => c)).size;
const now = new Date();
const currentMonth = String(now.getMonth() + 1).padStart(2, '0');
const currentYear = now.getFullYear();
@ -869,39 +869,39 @@ document.addEventListener('DOMContentLoaded', function() {
return year === currentYear && month === currentMonth;
}).map(c => c.intendedProduct).filter(c => c)
).size;
const products = new Set(customers.map(c => c.intendedProduct)).size;
const completed = new Set(
customers.filter(c =>
customers.filter(c =>
c.statusProgress && (c.statusProgress.includes('已修复') || c.statusProgress.includes('完成') || c.statusProgress.toLowerCase().includes('complete'))
).map(c => c.intendedProduct).filter(c => c)
).size;
document.getElementById('totalCustomers').textContent = totalCustomers;
document.getElementById('newCustomers').textContent = newCustomers;
document.getElementById('totalProducts').textContent = products;
document.getElementById('completedTasks').textContent = completed;
}
function renderStatusChart(customers) {
const ctx = document.getElementById('statusChart').getContext('2d');
const selectedField = document.getElementById('chartFieldSelect').value;
const chartTitle = document.getElementById('statusChartTitle').value || '数据分布';
const fieldCount = {};
customers.forEach(customer => {
const value = customer[selectedField] || '未设置';
fieldCount[value] = (fieldCount[value] || 0) + 1;
});
const labels = Object.keys(fieldCount);
const data = Object.values(fieldCount);
if (statusChartInstance) {
statusChartInstance.destroy();
}
statusChartInstance = new Chart(ctx, {
type: 'pie',
data: {
@ -940,38 +940,38 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
}
// Render customer type chart
function renderTypeChart(customers) {
console.log('renderTypeChart called with customers:', customers);
const canvas = document.getElementById('typeChart');
console.log('typeChart canvas element:', canvas);
if (!canvas) {
console.error('typeChart canvas not found');
return;
}
const ctx = canvas.getContext('2d');
const chartTitle = document.getElementById('typeChartTitle').value || '客户类型';
if (typeChartInstance) {
typeChartInstance.destroy();
}
const selectedField = document.getElementById('typeChartFieldSelect').value;
const typeCount = {};
customers.forEach(customer => {
const type = customer[selectedField] || '未设置';
typeCount[type] = (typeCount[type] || 0) + 1;
});
const labels = Object.keys(typeCount);
const data = Object.values(typeCount);
console.log('Type chart labels:', labels);
console.log('Type chart data:', data);
typeChartInstance = new Chart(ctx, {
type: 'doughnut',
data: {
@ -1005,15 +1005,15 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
});
console.log('Type chart created successfully');
}
// Initialize the app
loadCustomers().then(() => {
loadAllCustomers();
});
// Pagination event listeners
document.getElementById('firstPage').addEventListener('click', () => {
if (currentPage > 1) {
@ -1021,43 +1021,43 @@ document.addEventListener('DOMContentLoaded', function() {
applyCustomerFilter();
}
});
document.getElementById('prevPage').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
applyCustomerFilter();
}
});
document.getElementById('nextPage').addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
applyCustomerFilter();
}
});
document.getElementById('lastPage').addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage = totalPages;
applyCustomerFilter();
}
});
document.getElementById('pageSizeSelect').addEventListener('change', (e) => {
pageSize = parseInt(e.target.value);
currentPage = 1;
loadCustomers();
});
// Chart field select event listener
document.getElementById('chartFieldSelect').addEventListener('change', function() {
document.getElementById('chartFieldSelect').addEventListener('change', function () {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate);
});
// Type chart field select event listener
document.getElementById('typeChartFieldSelect').addEventListener('change', function() {
document.getElementById('typeChartFieldSelect').addEventListener('change', function () {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate);
@ -1074,7 +1074,7 @@ document.addEventListener('DOMContentLoaded', function() {
window.location.href = '/static/login.html';
}
});
const headerRight = document.querySelector('.header-right');
if (headerRight) {
headerRight.appendChild(logoutBtn);

View File

@ -1,14 +1,14 @@
package storage
import (
"crm-go/models"
"crypto/rand"
"encoding/hex"
"encoding/json"
"os"
"sync"
"path/filepath"
"sync"
"time"
"crm-go/models"
)
type CustomerStorage interface {
@ -31,58 +31,72 @@ func NewCustomerStorage(filePath string) CustomerStorage {
storage := &customerStorage{
filePath: filePath,
}
// Ensure the directory exists
dir := os.DirFS(filePath[:len(filePath)-len("/customers.json")])
_ = dir // Use the directory to ensure it exists
return storage
}
func (cs *customerStorage) GetAllCustomers() ([]models.Customer, error) {
cs.mutex.RLock()
defer cs.mutex.RUnlock()
return cs.LoadCustomers()
customers, err := cs.LoadCustomers()
if err != nil {
return nil, err
}
// Sort by CreatedAt in descending order (newest first)
for i := 0; i < len(customers)-1; i++ {
for j := i + 1; j < len(customers); j++ {
if customers[i].CreatedAt.Before(customers[j].CreatedAt) {
customers[i], customers[j] = customers[j], customers[i]
}
}
}
return customers, nil
}
func (cs *customerStorage) GetCustomerByID(id string) (*models.Customer, error) {
cs.mutex.RLock()
defer cs.mutex.RUnlock()
customers, err := cs.LoadCustomers()
if err != nil {
return nil, err
}
for _, customer := range customers {
if customer.ID == id {
return &customer, nil
}
}
return nil, nil
}
func (cs *customerStorage) CreateCustomer(customer models.Customer) error {
cs.mutex.Lock()
defer cs.mutex.Unlock()
if customer.ID == "" {
customer.ID = generateUUID()
}
if customer.CreatedAt.IsZero() {
customer.CreatedAt = time.Now()
}
customers, err := cs.LoadCustomers()
if err != nil {
return err
}
customers = append(customers, customer)
return cs.SaveCustomers(customers)
}
@ -91,19 +105,19 @@ func generateUUID() string {
rand.Read(bytes)
bytes[6] = (bytes[6] & 0x0f) | 0x40 // Version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80 // Variant
return hex.EncodeToString(bytes)
}
func (cs *customerStorage) UpdateCustomer(id string, updates models.UpdateCustomerRequest) error {
cs.mutex.Lock()
defer cs.mutex.Unlock()
customers, err := cs.LoadCustomers()
if err != nil {
return err
}
for i, customer := range customers {
if customer.ID == id {
if updates.CustomerName != nil {
@ -133,30 +147,30 @@ func (cs *customerStorage) UpdateCustomer(id string, updates models.UpdateCustom
if updates.Reporter != nil {
customers[i].Reporter = *updates.Reporter
}
return cs.SaveCustomers(customers)
}
}
return nil // Customer not found, but not an error
}
func (cs *customerStorage) DeleteCustomer(id string) error {
cs.mutex.Lock()
defer cs.mutex.Unlock()
customers, err := cs.LoadCustomers()
if err != nil {
return err
}
for i, customer := range customers {
if customer.ID == id {
customers = append(customers[:i], customers[i+1:]...)
return cs.SaveCustomers(customers)
}
}
return nil // Customer not found, but not an error
}
@ -166,12 +180,12 @@ func (cs *customerStorage) SaveCustomers(customers []models.Customer) error {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
data, err := json.MarshalIndent(customers, "", " ")
if err != nil {
return err
}
return os.WriteFile(cs.filePath, data, 0644)
}
@ -181,34 +195,34 @@ func (cs *customerStorage) LoadCustomers() ([]models.Customer, error) {
// Return empty slice if file doesn't exist
return []models.Customer{}, nil
}
data, err := os.ReadFile(cs.filePath)
if err != nil {
return nil, err
}
var customers []models.Customer
if err := json.Unmarshal(data, &customers); err != nil {
return nil, err
}
return customers, nil
}
func (cs *customerStorage) CustomerExists(customer models.Customer) (bool, error) {
cs.mutex.RLock()
defer cs.mutex.RUnlock()
customers, err := cs.LoadCustomers()
if err != nil {
return false, err
}
for _, existingCustomer := range customers {
if existingCustomer.Description == customer.Description {
return true, nil
}
}
return false, nil
}
}