diff --git a/frontend/index.html b/frontend/index.html
index 84b2676..a82e7e4 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -107,7 +107,6 @@
- | 时间 |
客户 |
版本 |
描述 |
@@ -116,6 +115,7 @@
模块 |
状态与进度 |
报告人 |
+ 时间 |
操作 |
diff --git a/frontend/js/main.js b/frontend/js/main.js
index 18dea1f..9c2e740 100644
--- a/frontend/js/main.js
+++ b/frontend/js/main.js
@@ -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 = '';
-
+
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 = `
`;
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);
diff --git a/internal/storage/customer_storage.go b/internal/storage/customer_storage.go
index 90ce9ce..94241e6 100644
--- a/internal/storage/customer_storage.go
+++ b/internal/storage/customer_storage.go
@@ -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
-}
\ No newline at end of file
+}