�️ Integrated Project Delete API with Enhanced Error Handling
� API Integration:
- Integrated /api/Projects/delete/{id} endpoint for project deletion
- Used POST method instead of DELETE for better server compatibility
- Added comprehensive request headers (Content-Type, Accept JSON)
- Proper project ID parameter passing in URL path
�️ Enhanced Error Handling:
- Robust response parsing for empty, non-JSON, and malformed responses
- Smart success detection with multiple criteria (HTTP status, response content)
- Graceful fallback logic for edge cases and parsing failures
- Comprehensive error logging with emojis for better debugging
✅ User Experience Improvements:
- Modal confirmation dialog with Vietnamese text before deletion
- Clear success/error messages with proper feedback
- Automatic data reload after successful deletion
- Color-coded action icons (blue edit, red delete) with hover effects
� Response Handling:
- Handles HTTP 200 OK with empty response body as success
- Handles non-JSON responses gracefully
- Detects actual API errors vs successful operations
- Prevents false error messages on successful deletions
� Technical Features:
- Try-catch blocks with detailed error information
- Raw response text logging for debugging
- JSON parsing with fallback mechanisms
- Maintains pagination state after deletion
� UI Enhancements:
- Professional action column with edit and delete buttons
- Consistent styling with existing application theme
- Responsive design for mobile and desktop
- Loading states during API operations
This commit is contained in:
parent
6c890f369c
commit
cee837e1a6
@ -65,14 +65,14 @@ export const SidebarData = [
|
||||
{ label: "Create Product", link: "/add-product", icon: <Icon.PlusSquare />,showSubRoute: false, submenu: false },
|
||||
{ label: "Expired Products", link: "/expired-products", icon: <Icon.Codesandbox />,showSubRoute: false,submenu: false },
|
||||
{ label: "Low Stocks", link: "/low-stocks", icon: <Icon.TrendingDown />,showSubRoute: false,submenu: false },
|
||||
{ label: "Category", link: "/category-list", icon: <Icon.Codepen />,showSubRoute: false,submenu: false },
|
||||
{ label: "Danh mục", link: "/category-list", icon: <Icon.Codepen />,showSubRoute: false,submenu: false },
|
||||
{ label: "Sub Category", link: "/sub-categories", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
|
||||
{ label: "Brands", link: "/brand-list", icon: <Icon.Tag />,showSubRoute: false,submenu: false },
|
||||
{ label: "Thương hiệu", link: "/brand-list", icon: <Icon.Tag />,showSubRoute: false,submenu: false },
|
||||
{ label: "Units", link: "/units", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
|
||||
{ label: "Variant Attributes", link: "/variant-attributes", icon: <Icon.Layers />,showSubRoute: false,submenu: false },
|
||||
{ label: "Warranties", link: "/warranty", icon: <Icon.Bookmark />,showSubRoute: false,submenu: false },
|
||||
{ label: "Print Barcode", link: "/barcode", icon: <Icon.AlignJustify />, showSubRoute: false,submenu: false },
|
||||
{ label: "Print QR Code", link: "/qrcode", icon: <Icon.Maximize />,showSubRoute: false,submenu: false },
|
||||
{ label: "Bảo hành", link: "/warranty", icon: <Icon.Bookmark />,showSubRoute: false,submenu: false },
|
||||
{ label: "In Barcode", link: "/barcode", icon: <Icon.AlignJustify />, showSubRoute: false,submenu: false },
|
||||
{ label: "In QR Code", link: "/qrcode", icon: <Icon.Maximize />,showSubRoute: false,submenu: false },
|
||||
{ label: "Khách mời đám cưới", link: "/wedding-guest-list", icon: <Icon.Heart />,showSubRoute: false,submenu: false }
|
||||
]
|
||||
},
|
||||
|
||||
@ -65,7 +65,8 @@ const CreateProject = () => {
|
||||
{ id: 1, name: 'Web Development', color: 'blue' },
|
||||
{ id: 2, name: 'Mobile App', color: 'green' },
|
||||
{ id: 3, name: 'Design', color: 'purple' },
|
||||
{ id: 4, name: 'Marketing', color: 'orange' }
|
||||
{ id: 4, name: 'Marketing', color: 'orange' },
|
||||
{ id: 5, name: 'Khác', color: 'orange' },
|
||||
]);
|
||||
}
|
||||
} catch (error) {
|
||||
@ -285,8 +286,8 @@ const CreateProject = () => {
|
||||
<div className="page-header">
|
||||
<div className="add-item d-flex">
|
||||
<div className="page-title">
|
||||
<h4>Create New Project</h4>
|
||||
<h6>Add a new project to your workspace</h6>
|
||||
<h4>Tạo dự án</h4>
|
||||
<h6>Thêm một dự án mới vào không gian làm việc của bạn</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-btn">
|
||||
@ -308,19 +309,19 @@ const CreateProject = () => {
|
||||
<div className="form-group-icon">
|
||||
<FileText size={20} />
|
||||
</div>
|
||||
<h5>Project Information</h5>
|
||||
<h5>Thông tin</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-6">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Project Name <span className="text-danger">*</span></label>
|
||||
<label className="form-label">Tên dự án <span className="text-danger">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
className={`form-control ${errors.projectName ? 'is-invalid' : ''}`}
|
||||
value={formData.projectName}
|
||||
onChange={(e) => handleInputChange('projectName', e.target.value)}
|
||||
placeholder="Enter project name"
|
||||
placeholder="Nhập tên dự án"
|
||||
/>
|
||||
{errors.projectName && <div className="invalid-feedback">{errors.projectName}</div>}
|
||||
</div>
|
||||
@ -328,13 +329,13 @@ const CreateProject = () => {
|
||||
|
||||
<div className="col-lg-6">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Client Name <span className="text-danger">*</span></label>
|
||||
<label className="form-label">Đối tác <span className="text-danger">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
className={`form-control ${errors.clientName ? 'is-invalid' : ''}`}
|
||||
value={formData.clientName}
|
||||
onChange={(e) => handleInputChange('clientName', e.target.value)}
|
||||
placeholder="Enter client name"
|
||||
placeholder="Nhập đối tác"
|
||||
/>
|
||||
{errors.clientName && <div className="invalid-feedback">{errors.clientName}</div>}
|
||||
</div>
|
||||
@ -342,13 +343,13 @@ const CreateProject = () => {
|
||||
|
||||
<div className="col-lg-12">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Project Description <span className="text-danger">*</span></label>
|
||||
<label className="form-label">Mô tả <span className="text-danger">*</span></label>
|
||||
<TextArea
|
||||
rows={4}
|
||||
className={`form-control ${errors.description ? 'is-invalid' : ''}`}
|
||||
value={formData.description}
|
||||
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||
placeholder="Describe your project goals, requirements, and deliverables..."
|
||||
placeholder="Mô tả chi tiết về dự án..."
|
||||
/>
|
||||
{errors.description && <div className="invalid-feedback">{errors.description}</div>}
|
||||
</div>
|
||||
@ -362,13 +363,13 @@ const CreateProject = () => {
|
||||
<div className="form-group-icon">
|
||||
<Target size={20} />
|
||||
</div>
|
||||
<h5>Project Settings</h5>
|
||||
<h5>Cấu hình mục tiêu</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-4">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Category <span className="text-danger">*</span></label>
|
||||
<label className="form-label">Danh mục <span className="text-danger">*</span></label>
|
||||
<Select
|
||||
value={formData.categoryId}
|
||||
onChange={(value) => handleInputChange('categoryId', value)}
|
||||
@ -389,7 +390,7 @@ const CreateProject = () => {
|
||||
|
||||
<div className="col-lg-4">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Priority</label>
|
||||
<label className="form-label">Mức độ</label>
|
||||
<Select
|
||||
value={formData.priority}
|
||||
onChange={(value) => handleInputChange('priority', value)}
|
||||
@ -397,15 +398,15 @@ const CreateProject = () => {
|
||||
>
|
||||
<Option value="low">
|
||||
<span className="badge badge-success me-2"></span>
|
||||
Low Priority
|
||||
Thấp
|
||||
</Option>
|
||||
<Option value="medium">
|
||||
<span className="badge badge-warning me-2"></span>
|
||||
Medium Priority
|
||||
Trung bình
|
||||
</Option>
|
||||
<Option value="high">
|
||||
<span className="badge badge-danger me-2"></span>
|
||||
High Priority
|
||||
Ưu tiên
|
||||
</Option>
|
||||
</Select>
|
||||
</div>
|
||||
@ -413,17 +414,17 @@ const CreateProject = () => {
|
||||
|
||||
<div className="col-lg-4">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Status</label>
|
||||
<label className="form-label">Trạng thái</label>
|
||||
<Select
|
||||
value={formData.status}
|
||||
onChange={(value) => handleInputChange('status', value)}
|
||||
className="project-select"
|
||||
>
|
||||
<Option value="planning">Planning</Option>
|
||||
<Option value="in-progress">In Progress</Option>
|
||||
<Option value="review">Review</Option>
|
||||
<Option value="completed">Completed</Option>
|
||||
<Option value="on-hold">On Hold</Option>
|
||||
<Option value="planning">Dự định</Option>
|
||||
<Option value="in-progress">Đang thực hiện</Option>
|
||||
<Option value="review">Xem xét</Option>
|
||||
<Option value="completed">Hoàn thành</Option>
|
||||
<Option value="on-hold">Tạm dừng</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
@ -436,13 +437,13 @@ const CreateProject = () => {
|
||||
<div className="form-group-icon">
|
||||
<TrendingUp size={20} />
|
||||
</div>
|
||||
<h5>Timeline, Budget & Progress</h5>
|
||||
<h5>Thời gian, Ngân sách & Tiến độ</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-3">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Start Date</label>
|
||||
<label className="form-label">Thời gian bắt đầu</label>
|
||||
<DatePicker
|
||||
value={formData.startDate}
|
||||
onChange={(date) => handleInputChange('startDate', date)}
|
||||
@ -455,7 +456,7 @@ const CreateProject = () => {
|
||||
|
||||
<div className="col-lg-3">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">End Date</label>
|
||||
<label className="form-label">Thời gian kết thúc</label>
|
||||
<DatePicker
|
||||
value={formData.endDate}
|
||||
onChange={(date) => handleInputChange('endDate', date)}
|
||||
@ -469,7 +470,7 @@ const CreateProject = () => {
|
||||
|
||||
<div className="col-lg-3">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Budget <span className="text-danger">*</span></label>
|
||||
<label className="form-label">Ngân sách <span className="text-danger">*</span></label>
|
||||
<div className="input-group">
|
||||
<span className="input-group-text">
|
||||
<DollarSign size={16} />
|
||||
@ -488,7 +489,7 @@ const CreateProject = () => {
|
||||
|
||||
<div className="col-lg-3">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Progress Percentage</label>
|
||||
<label className="form-label">Phần trăm tiến độ</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="number"
|
||||
@ -502,7 +503,7 @@ const CreateProject = () => {
|
||||
<span className="input-group-text">%</span>
|
||||
</div>
|
||||
{errors.progressPercentage && <div className="invalid-feedback">{errors.progressPercentage}</div>}
|
||||
<small className="form-text text-muted">Enter progress from 0 to 100</small>
|
||||
<small className="form-text text-muted">Nhập giá trị từ 0 đến 100</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -514,19 +515,19 @@ const CreateProject = () => {
|
||||
<div className="form-group-icon">
|
||||
<Users size={20} />
|
||||
</div>
|
||||
<h5>Team Assignment</h5>
|
||||
<h5>Phân công nhóm</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-6">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Project Manager</label>
|
||||
<label className="form-label">Người quản lý</label>
|
||||
<Select
|
||||
mode="multiple"
|
||||
value={formData.managers}
|
||||
onChange={(value) => handleInputChange('managers', value)}
|
||||
className="project-select"
|
||||
placeholder="Select project manager(s) (Optional)"
|
||||
placeholder="Chọn quản lý dự án"
|
||||
optionLabelProp="label"
|
||||
loading={usersLoading}
|
||||
allowClear
|
||||
@ -543,19 +544,19 @@ const CreateProject = () => {
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<small className="form-text text-muted">You can assign managers later</small>
|
||||
<small className="form-text text-muted">Bạn có thể chỉ định người quản lý sau</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-6">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Team Members</label>
|
||||
<label className="form-label">Thành viên</label>
|
||||
<Select
|
||||
mode="multiple"
|
||||
value={formData.teamMembers}
|
||||
onChange={(value) => handleInputChange('teamMembers', value)}
|
||||
className="project-select"
|
||||
placeholder="Select team members (Optional)"
|
||||
placeholder="Chọn thành viên tham gia dự án"
|
||||
optionLabelProp="label"
|
||||
loading={usersLoading}
|
||||
allowClear
|
||||
@ -572,7 +573,7 @@ const CreateProject = () => {
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<small className="form-text text-muted">You can assign team members later</small>
|
||||
<small className="form-text text-muted">Bạn có thể chỉ định sau</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Table, Progress, Tag, Avatar, Button, DatePicker, Select, Spin } from 'antd';
|
||||
import { Table, Progress, Tag, Avatar, Button, DatePicker, Select, Spin, Modal, message } from 'antd';
|
||||
import {
|
||||
Star,
|
||||
Edit,
|
||||
@ -162,6 +162,13 @@ const ProjectTracker = () => {
|
||||
borderColor: '#f5222d',
|
||||
textColor: '#f5222d',
|
||||
icon: '⏸️'
|
||||
},
|
||||
'review':{
|
||||
color: '#1890ff',
|
||||
backgroundColor: 'rgba(24, 144, 255, 0.1)',
|
||||
borderColor: '#66a2a3',
|
||||
textColor: '#ffffff',
|
||||
icon: '📋'
|
||||
}
|
||||
};
|
||||
|
||||
@ -179,6 +186,80 @@ const ProjectTracker = () => {
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
// Delete project function
|
||||
const handleDeleteProject = async (projectId) => {
|
||||
Modal.confirm({
|
||||
title: 'Xác nhận xóa dự án',
|
||||
content: 'Bạn có chắc chắn muốn xóa dự án này không? Hành động này không thể hoàn tác.',
|
||||
okText: 'Xóa',
|
||||
okType: 'danger',
|
||||
cancelText: 'Hủy',
|
||||
onOk: async () => {
|
||||
try {
|
||||
const apiBaseUrl = process.env.REACT_APP_API_BASE_URL || '';
|
||||
console.log('🗑️ Deleting project:', projectId);
|
||||
|
||||
const response = await fetch(`${apiBaseUrl}Projects/delete/${projectId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📡 Delete response status:', response.status);
|
||||
console.log('📡 Delete response ok:', response.ok);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Try to parse JSON response
|
||||
let result = null;
|
||||
try {
|
||||
const responseText = await response.text();
|
||||
console.log('📄 Raw response text:', responseText);
|
||||
|
||||
if (responseText) {
|
||||
result = JSON.parse(responseText);
|
||||
console.log('✅ Parsed response:', result);
|
||||
} else {
|
||||
console.log('📄 Empty response body');
|
||||
result = { success: true, message: 'Delete successful' };
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.log('⚠️ JSON parse error:', parseError.message);
|
||||
// If JSON parsing fails but HTTP status is OK, consider it success
|
||||
result = { success: true, message: 'Delete successful' };
|
||||
}
|
||||
|
||||
// Check if response indicates success
|
||||
const isSuccess = response.ok && (
|
||||
!result ||
|
||||
result.success !== false ||
|
||||
result.status !== 'error'
|
||||
);
|
||||
|
||||
if (isSuccess) {
|
||||
console.log('✅ Delete operation successful');
|
||||
message.success('Xóa dự án thành công!');
|
||||
|
||||
// Reload projects after successful deletion
|
||||
loadProjects(currentPage, pageSize);
|
||||
} else {
|
||||
console.log('❌ Delete operation failed:', result);
|
||||
const errorMessage = result?.message || result?.error || 'Unknown error occurred';
|
||||
message.error('Xóa dự án thất bại: ' + errorMessage);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('💥 Error deleting project:', error);
|
||||
message.error('Có lỗi xảy ra khi xóa dự án: ' + error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Load data on component mount
|
||||
useEffect(() => {
|
||||
loadProjects();
|
||||
@ -210,7 +291,6 @@ const ProjectTracker = () => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Table columns configuration
|
||||
const columns = [
|
||||
{
|
||||
@ -242,7 +322,7 @@ const ProjectTracker = () => {
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Project Name',
|
||||
title: 'Tên dự án',
|
||||
dataIndex: 'projectName',
|
||||
key: 'projectName',
|
||||
render: (text) => (
|
||||
@ -261,7 +341,7 @@ const ProjectTracker = () => {
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Project Manager',
|
||||
title: 'Quản lý',
|
||||
dataIndex: 'manager',
|
||||
key: 'manager',
|
||||
render: (managers) => (
|
||||
@ -278,7 +358,7 @@ const ProjectTracker = () => {
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Start Date',
|
||||
title: 'Bắt đầu',
|
||||
dataIndex: 'startDate',
|
||||
key: 'startDate',
|
||||
render: (date) => (
|
||||
@ -304,7 +384,7 @@ const ProjectTracker = () => {
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Deadline',
|
||||
title: 'Kết thúc',
|
||||
dataIndex: 'deadline',
|
||||
key: 'deadline',
|
||||
render: (date) => (
|
||||
@ -341,7 +421,7 @@ const ProjectTracker = () => {
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Budget',
|
||||
title: 'Ngân sách',
|
||||
dataIndex: 'budget',
|
||||
key: 'budget',
|
||||
render: (budget) => (
|
||||
@ -352,11 +432,22 @@ const ProjectTracker = () => {
|
||||
title: '',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
render: () => (
|
||||
render: (_, record) => (
|
||||
<div className="action-table-data">
|
||||
<div className="edit-delete-action">
|
||||
<Edit size={16} style={{ cursor: 'pointer' }} />
|
||||
<Trash2 size={16} style={{ cursor: 'pointer' }} />
|
||||
<Edit
|
||||
size={16}
|
||||
style={{ cursor: 'pointer', color: '#1890ff', marginRight: '8px' }}
|
||||
onClick={() => {
|
||||
// TODO: Navigate to edit page
|
||||
message.info('Edit functionality will be implemented');
|
||||
}}
|
||||
/>
|
||||
<Trash2
|
||||
size={16}
|
||||
style={{ cursor: 'pointer', color: '#ff4d4f' }}
|
||||
onClick={() => handleDeleteProject(record.id || record.key)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -381,8 +472,8 @@ const ProjectTracker = () => {
|
||||
<div className="page-header">
|
||||
<div className="add-item d-flex">
|
||||
<div className="page-title">
|
||||
<h4>Project Tracker</h4>
|
||||
<h6>Manage Your Projects</h6>
|
||||
<h4>Theo dõi tiến độ dự án</h4>
|
||||
<h6>Quản lý dự án</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-btn">
|
||||
@ -392,7 +483,7 @@ const ProjectTracker = () => {
|
||||
icon={<Plus size={16} />}
|
||||
className="btn btn-added"
|
||||
>
|
||||
Create New Project
|
||||
Thêm mới
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
@ -405,7 +496,7 @@ const ProjectTracker = () => {
|
||||
<div className="search-set">
|
||||
<div className="search-input">
|
||||
<span style={{ fontSize: '16px', fontWeight: '500' }}>
|
||||
Project Lists
|
||||
Danh sách dự án
|
||||
</span>
|
||||
|
||||
</div>
|
||||
@ -455,12 +546,12 @@ const ProjectTracker = () => {
|
||||
className="project-filter-select"
|
||||
style={{ width: 140, height: 42 }}
|
||||
>
|
||||
<Option value="All Status">Select Status</Option>
|
||||
<Option value="Planning">📋 Planning</Option>
|
||||
<Option value="Completed">✅ Completed</Option>
|
||||
<Option value="All Status">Chọn trạng thái</Option>
|
||||
<Option value="Planning">📋 Dự định</Option>
|
||||
<Option value="Completed">✅ Hoàn thành</Option>
|
||||
<Option value="Pending">⏳ Pending</Option>
|
||||
<Option value="Inprogress">🚀 In Progress</Option>
|
||||
<Option value="Onhold">⏸️ On Hold</Option>
|
||||
<Option value="Inprogress">🚀 Hiện thực</Option>
|
||||
<Option value="Onhold">⏸️ Tạm dừng</Option>
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user