�️ 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:
tuanOts 2025-06-08 23:02:58 +07:00
parent 6c890f369c
commit cee837e1a6
3 changed files with 151 additions and 59 deletions

View File

@ -65,14 +65,14 @@ export const SidebarData = [
{ label: "Create Product", link: "/add-product", icon: <Icon.PlusSquare />,showSubRoute: false, submenu: false }, { 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: "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: "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: "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: "Units", link: "/units", icon: <Icon.Speaker />,showSubRoute: false,submenu: false },
{ label: "Variant Attributes", link: "/variant-attributes", icon: <Icon.Layers />,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: "Bảo hành", link: "/warranty", icon: <Icon.Bookmark />,showSubRoute: false,submenu: false },
{ label: "Print Barcode", link: "/barcode", icon: <Icon.AlignJustify />, showSubRoute: false,submenu: false }, { label: "In Barcode", link: "/barcode", icon: <Icon.AlignJustify />, showSubRoute: false,submenu: false },
{ label: "Print QR Code", link: "/qrcode", icon: <Icon.Maximize />,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 } { label: "Khách mời đám cưới", link: "/wedding-guest-list", icon: <Icon.Heart />,showSubRoute: false,submenu: false }
] ]
}, },

View File

@ -65,7 +65,8 @@ const CreateProject = () => {
{ id: 1, name: 'Web Development', color: 'blue' }, { id: 1, name: 'Web Development', color: 'blue' },
{ id: 2, name: 'Mobile App', color: 'green' }, { id: 2, name: 'Mobile App', color: 'green' },
{ id: 3, name: 'Design', color: 'purple' }, { 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) { } catch (error) {
@ -285,8 +286,8 @@ const CreateProject = () => {
<div className="page-header"> <div className="page-header">
<div className="add-item d-flex"> <div className="add-item d-flex">
<div className="page-title"> <div className="page-title">
<h4>Create New Project</h4> <h4>Tạo dự án</h4>
<h6>Add a new project to your workspace</h6> <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> </div>
<div className="page-btn"> <div className="page-btn">
@ -308,19 +309,19 @@ const CreateProject = () => {
<div className="form-group-icon"> <div className="form-group-icon">
<FileText size={20} /> <FileText size={20} />
</div> </div>
<h5>Project Information</h5> <h5>Thông tin</h5>
</div> </div>
</div> </div>
<div className="col-lg-6"> <div className="col-lg-6">
<div className="mb-3"> <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 <input
type="text" type="text"
className={`form-control ${errors.projectName ? 'is-invalid' : ''}`} className={`form-control ${errors.projectName ? 'is-invalid' : ''}`}
value={formData.projectName} value={formData.projectName}
onChange={(e) => handleInputChange('projectName', e.target.value)} 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>} {errors.projectName && <div className="invalid-feedback">{errors.projectName}</div>}
</div> </div>
@ -328,13 +329,13 @@ const CreateProject = () => {
<div className="col-lg-6"> <div className="col-lg-6">
<div className="mb-3"> <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 <input
type="text" type="text"
className={`form-control ${errors.clientName ? 'is-invalid' : ''}`} className={`form-control ${errors.clientName ? 'is-invalid' : ''}`}
value={formData.clientName} value={formData.clientName}
onChange={(e) => handleInputChange('clientName', e.target.value)} 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>} {errors.clientName && <div className="invalid-feedback">{errors.clientName}</div>}
</div> </div>
@ -342,13 +343,13 @@ const CreateProject = () => {
<div className="col-lg-12"> <div className="col-lg-12">
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Project Description <span className="text-danger">*</span></label> <label className="form-label">tả <span className="text-danger">*</span></label>
<TextArea <TextArea
rows={4} rows={4}
className={`form-control ${errors.description ? 'is-invalid' : ''}`} className={`form-control ${errors.description ? 'is-invalid' : ''}`}
value={formData.description} value={formData.description}
onChange={(e) => handleInputChange('description', e.target.value)} 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>} {errors.description && <div className="invalid-feedback">{errors.description}</div>}
</div> </div>
@ -362,13 +363,13 @@ const CreateProject = () => {
<div className="form-group-icon"> <div className="form-group-icon">
<Target size={20} /> <Target size={20} />
</div> </div>
<h5>Project Settings</h5> <h5>Cấu hình mục tiêu</h5>
</div> </div>
</div> </div>
<div className="col-lg-4"> <div className="col-lg-4">
<div className="mb-3"> <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 <Select
value={formData.categoryId} value={formData.categoryId}
onChange={(value) => handleInputChange('categoryId', value)} onChange={(value) => handleInputChange('categoryId', value)}
@ -389,7 +390,7 @@ const CreateProject = () => {
<div className="col-lg-4"> <div className="col-lg-4">
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Priority</label> <label className="form-label">Mức độ</label>
<Select <Select
value={formData.priority} value={formData.priority}
onChange={(value) => handleInputChange('priority', value)} onChange={(value) => handleInputChange('priority', value)}
@ -397,15 +398,15 @@ const CreateProject = () => {
> >
<Option value="low"> <Option value="low">
<span className="badge badge-success me-2"></span> <span className="badge badge-success me-2"></span>
Low Priority Thấp
</Option> </Option>
<Option value="medium"> <Option value="medium">
<span className="badge badge-warning me-2"></span> <span className="badge badge-warning me-2"></span>
Medium Priority Trung bình
</Option> </Option>
<Option value="high"> <Option value="high">
<span className="badge badge-danger me-2"></span> <span className="badge badge-danger me-2"></span>
High Priority Ưu tiên
</Option> </Option>
</Select> </Select>
</div> </div>
@ -413,17 +414,17 @@ const CreateProject = () => {
<div className="col-lg-4"> <div className="col-lg-4">
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Status</label> <label className="form-label">Trạng thái</label>
<Select <Select
value={formData.status} value={formData.status}
onChange={(value) => handleInputChange('status', value)} onChange={(value) => handleInputChange('status', value)}
className="project-select" className="project-select"
> >
<Option value="planning">Planning</Option> <Option value="planning">Dự định</Option>
<Option value="in-progress">In Progress</Option> <Option value="in-progress">Đang thực hiện</Option>
<Option value="review">Review</Option> <Option value="review">Xem xét</Option>
<Option value="completed">Completed</Option> <Option value="completed">Hoàn thành</Option>
<Option value="on-hold">On Hold</Option> <Option value="on-hold">Tạm dừng</Option>
</Select> </Select>
</div> </div>
</div> </div>
@ -436,13 +437,13 @@ const CreateProject = () => {
<div className="form-group-icon"> <div className="form-group-icon">
<TrendingUp size={20} /> <TrendingUp size={20} />
</div> </div>
<h5>Timeline, Budget & Progress</h5> <h5>Thời gian, Ngân sách & Tiến độ</h5>
</div> </div>
</div> </div>
<div className="col-lg-3"> <div className="col-lg-3">
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Start Date</label> <label className="form-label">Thời gian bắt đầu</label>
<DatePicker <DatePicker
value={formData.startDate} value={formData.startDate}
onChange={(date) => handleInputChange('startDate', date)} onChange={(date) => handleInputChange('startDate', date)}
@ -455,7 +456,7 @@ const CreateProject = () => {
<div className="col-lg-3"> <div className="col-lg-3">
<div className="mb-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 <DatePicker
value={formData.endDate} value={formData.endDate}
onChange={(date) => handleInputChange('endDate', date)} onChange={(date) => handleInputChange('endDate', date)}
@ -469,7 +470,7 @@ const CreateProject = () => {
<div className="col-lg-3"> <div className="col-lg-3">
<div className="mb-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"> <div className="input-group">
<span className="input-group-text"> <span className="input-group-text">
<DollarSign size={16} /> <DollarSign size={16} />
@ -488,7 +489,7 @@ const CreateProject = () => {
<div className="col-lg-3"> <div className="col-lg-3">
<div className="mb-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"> <div className="input-group">
<input <input
type="number" type="number"
@ -502,7 +503,7 @@ const CreateProject = () => {
<span className="input-group-text">%</span> <span className="input-group-text">%</span>
</div> </div>
{errors.progressPercentage && <div className="invalid-feedback">{errors.progressPercentage}</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> </div>
</div> </div>
@ -514,19 +515,19 @@ const CreateProject = () => {
<div className="form-group-icon"> <div className="form-group-icon">
<Users size={20} /> <Users size={20} />
</div> </div>
<h5>Team Assignment</h5> <h5>Phân công nhóm</h5>
</div> </div>
</div> </div>
<div className="col-lg-6"> <div className="col-lg-6">
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Project Manager</label> <label className="form-label">Người quản </label>
<Select <Select
mode="multiple" mode="multiple"
value={formData.managers} value={formData.managers}
onChange={(value) => handleInputChange('managers', value)} onChange={(value) => handleInputChange('managers', value)}
className="project-select" className="project-select"
placeholder="Select project manager(s) (Optional)" placeholder="Chọn quản lý dự án"
optionLabelProp="label" optionLabelProp="label"
loading={usersLoading} loading={usersLoading}
allowClear allowClear
@ -543,19 +544,19 @@ const CreateProject = () => {
</Option> </Option>
))} ))}
</Select> </Select>
<small className="form-text text-muted">You can assign managers later</small> <small className="form-text text-muted">Bạn thể chỉ định người quản sau</small>
</div> </div>
</div> </div>
<div className="col-lg-6"> <div className="col-lg-6">
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Team Members</label> <label className="form-label">Thành viên</label>
<Select <Select
mode="multiple" mode="multiple"
value={formData.teamMembers} value={formData.teamMembers}
onChange={(value) => handleInputChange('teamMembers', value)} onChange={(value) => handleInputChange('teamMembers', value)}
className="project-select" className="project-select"
placeholder="Select team members (Optional)" placeholder="Chọn thành viên tham gia dự án"
optionLabelProp="label" optionLabelProp="label"
loading={usersLoading} loading={usersLoading}
allowClear allowClear
@ -572,7 +573,7 @@ const CreateProject = () => {
</Option> </Option>
))} ))}
</Select> </Select>
<small className="form-text text-muted">You can assign team members later</small> <small className="form-text text-muted">Bạn thể chỉ định sau</small>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'; 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 { import {
Star, Star,
Edit, Edit,
@ -162,6 +162,13 @@ const ProjectTracker = () => {
borderColor: '#f5222d', borderColor: '#f5222d',
textColor: '#f5222d', textColor: '#f5222d',
icon: '⏸️' 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; 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 // Load data on component mount
useEffect(() => { useEffect(() => {
loadProjects(); loadProjects();
@ -210,7 +291,6 @@ const ProjectTracker = () => {
}; };
// Table columns configuration // Table columns configuration
const columns = [ const columns = [
{ {
@ -242,7 +322,7 @@ const ProjectTracker = () => {
) )
}, },
{ {
title: 'Project Name', title: 'Tên dự án',
dataIndex: 'projectName', dataIndex: 'projectName',
key: 'projectName', key: 'projectName',
render: (text) => ( render: (text) => (
@ -261,7 +341,7 @@ const ProjectTracker = () => {
) )
}, },
{ {
title: 'Project Manager', title: 'Quản lý',
dataIndex: 'manager', dataIndex: 'manager',
key: 'manager', key: 'manager',
render: (managers) => ( render: (managers) => (
@ -278,7 +358,7 @@ const ProjectTracker = () => {
) )
}, },
{ {
title: 'Start Date', title: 'Bắt đầu',
dataIndex: 'startDate', dataIndex: 'startDate',
key: 'startDate', key: 'startDate',
render: (date) => ( render: (date) => (
@ -304,7 +384,7 @@ const ProjectTracker = () => {
) )
}, },
{ {
title: 'Deadline', title: 'Kết thúc',
dataIndex: 'deadline', dataIndex: 'deadline',
key: 'deadline', key: 'deadline',
render: (date) => ( render: (date) => (
@ -341,7 +421,7 @@ const ProjectTracker = () => {
} }
}, },
{ {
title: 'Budget', title: 'Ngân sách',
dataIndex: 'budget', dataIndex: 'budget',
key: 'budget', key: 'budget',
render: (budget) => ( render: (budget) => (
@ -352,11 +432,22 @@ const ProjectTracker = () => {
title: '', title: '',
key: 'actions', key: 'actions',
width: 100, width: 100,
render: () => ( render: (_, record) => (
<div className="action-table-data"> <div className="action-table-data">
<div className="edit-delete-action"> <div className="edit-delete-action">
<Edit size={16} style={{ cursor: 'pointer' }} /> <Edit
<Trash2 size={16} style={{ cursor: 'pointer' }} /> 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>
</div> </div>
) )
@ -381,8 +472,8 @@ const ProjectTracker = () => {
<div className="page-header"> <div className="page-header">
<div className="add-item d-flex"> <div className="add-item d-flex">
<div className="page-title"> <div className="page-title">
<h4>Project Tracker</h4> <h4>Theo dõi tiến độ dự án</h4>
<h6>Manage Your Projects</h6> <h6>Quản dự án</h6>
</div> </div>
</div> </div>
<div className="page-btn"> <div className="page-btn">
@ -392,7 +483,7 @@ const ProjectTracker = () => {
icon={<Plus size={16} />} icon={<Plus size={16} />}
className="btn btn-added" className="btn btn-added"
> >
Create New Project Thêm mới
</Button> </Button>
</Link> </Link>
</div> </div>
@ -405,7 +496,7 @@ const ProjectTracker = () => {
<div className="search-set"> <div className="search-set">
<div className="search-input"> <div className="search-input">
<span style={{ fontSize: '16px', fontWeight: '500' }}> <span style={{ fontSize: '16px', fontWeight: '500' }}>
Project Lists Danh sách dự án
</span> </span>
</div> </div>
@ -455,12 +546,12 @@ const ProjectTracker = () => {
className="project-filter-select" className="project-filter-select"
style={{ width: 140, height: 42 }} style={{ width: 140, height: 42 }}
> >
<Option value="All Status">Select Status</Option> <Option value="All Status">Chọn trạng thái</Option>
<Option value="Planning">📋 Planning</Option> <Option value="Planning">📋 Dự định</Option>
<Option value="Completed"> Completed</Option> <Option value="Completed"> Hoàn thành</Option>
<Option value="Pending"> Pending</Option> <Option value="Pending"> Pending</Option>
<Option value="Inprogress">🚀 In Progress</Option> <Option value="Inprogress">🚀 Hiện thực</Option>
<Option value="Onhold"> On Hold</Option> <Option value="Onhold"> Tạm dừng</Option>
</Select> </Select>
<Select <Select